diff options
Diffstat (limited to 'spec/support')
64 files changed, 742 insertions, 220 deletions
diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb index d26d279363c..d2d6260dfa8 100644 --- a/spec/support/api/issues_resolving_discussions_shared_examples.rb +++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb @@ -1,6 +1,6 @@ shared_examples 'creating an issue resolving discussions through the API' do it 'creates a new project issue' do - expect(response).to have_http_status(:created) + expect(response).to have_gitlab_http_status(:created) end it 'resolves the discussions in a merge request' do diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb index dab71a35a55..8d910e52eda 100644 --- a/spec/support/api/members_shared_examples.rb +++ b/spec/support/api/members_shared_examples.rb @@ -6,6 +6,6 @@ shared_examples 'a 404 response when source is private' do it 'returns 404' do route - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 4bb5113957e..d9080b02541 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -10,7 +10,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns milestones list' do get api(route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['title']).to eq(milestone.title) @@ -19,13 +19,13 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a 401 error if user not authenticated' do get api(route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns an array of active milestones' do get api("#{route}/?state=active", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -35,7 +35,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns an array of closed milestones' do get api("#{route}/?state=closed", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -47,7 +47,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(route, user), iids: [closed_milestone.iid, other_milestone.iid] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) @@ -56,7 +56,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'does not return any milestone if none found' do get api(route, user), iids: [Milestone.maximum(:iid).succ] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -75,7 +75,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestone by searching for title' do get api(route, user), search: 'version2' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq milestone.title @@ -85,7 +85,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestones by searching for description' do get api(route, user), search: 'open' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq milestone.title @@ -97,7 +97,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestone by id' do get api(resource_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -105,7 +105,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestone by id' do get api(resource_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -113,13 +113,13 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns 401 error if user not authenticated' do get api(resource_route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 404 error if milestone id not found' do get api("#{route}/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -127,7 +127,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'creates a new milestone' do post api(route, user), title: 'new milestone' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil end @@ -136,7 +136,7 @@ shared_examples_for 'group and project milestones' do |route_definition| post api(route, user), title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['description']).to eq('release') expect(json_response['due_date']).to eq('2013-03-02') expect(json_response['start_date']).to eq('2013-02-02') @@ -145,20 +145,20 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a 400 error if title is missing' do post api(route, user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 error if params are invalid (duplicate title)' do post api(route, user), title: milestone.title, description: 'release', due_date: '2013-03-02' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'creates a new milestone with reserved html characters' do post api(route, user), title: 'foo & bar 1.1 -> 2.2' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') expect(json_response['description']).to be_nil end @@ -169,7 +169,7 @@ shared_examples_for 'group and project milestones' do |route_definition| put api(resource_route, user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -178,7 +178,7 @@ shared_examples_for 'group and project milestones' do |route_definition| put api(resource_route, user), due_date: nil - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to be_nil end @@ -186,13 +186,13 @@ shared_examples_for 'group and project milestones' do |route_definition| put api("#{route}/1234", user), title: 'updated title' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'closes milestone' do put api(resource_route, user), state_event: 'close' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq('closed') end @@ -207,7 +207,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns issues for a particular milestone' do get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['milestone']['title']).to eq(milestone.title) @@ -228,14 +228,14 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'matches V4 response schema for a list of issues' do get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/issues') end it 'returns a 401 error if user not authenticated' do get api(issues_route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end describe 'confidential issues' do @@ -265,7 +265,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns confidential issues to team members' do get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array # 2 for projects, 3 for group(which has another project with an issue) @@ -279,7 +279,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(issues_route, member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -289,7 +289,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'does not return confidential issues to regular users' do get api(issues_route, create(:user)) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -302,7 +302,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array # 2 for projects, 3 for group(which has another project with an issue) @@ -325,7 +325,7 @@ shared_examples_for 'group and project milestones' do |route_definition| another_merge_request get api(merge_requests_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq(merge_request.title) @@ -349,20 +349,20 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(not_found_route, user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 if the user has no access to the milestone' do new_user = create :user get api(merge_requests_route, new_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 401 error if user not authenticated' do get api(merge_requests_route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns merge_requests ordered by position asc' do @@ -372,7 +372,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(merge_requests_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(2) diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb index 57e28e040d7..06ae8792c61 100644 --- a/spec/support/api/scopes/read_user_shared_examples.rb +++ b/spec/support/api/scopes/read_user_shared_examples.rb @@ -6,7 +6,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, personal_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -16,7 +16,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, personal_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -27,10 +27,10 @@ shared_examples_for 'allows the "read_user" scope' do stub_container_registry_config(enabled: true) end - it 'returns a "401" response' do + it 'returns a "403" response' do get api_call.call(path, user, personal_access_token: token) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(403) end end end @@ -44,7 +44,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, oauth_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -54,7 +54,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, oauth_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -64,7 +64,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "403" response' do get api_call.call(path, user, oauth_access_token: token) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -74,10 +74,10 @@ shared_examples_for 'does not allow the "read_user" scope' do context 'when the requesting token has the "read_user" scope' do let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) } - it 'returns a "401" response' do + it 'returns a "403" response' do post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(403) end end end diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb index 16a3cf06be7..af1083f4bfd 100644 --- a/spec/support/api/time_tracking_shared_examples.rb +++ b/spec/support/api/time_tracking_shared_examples.rb @@ -15,7 +15,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "sets the time estimate for #{issuable_name}" do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['human_time_estimate']).to eq('1w') end @@ -28,7 +28,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it 'does not modify the original estimate' do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(issuable.reload.human_time_estimate).to eq('1w') end end @@ -37,7 +37,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it 'updates the estimate' do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '3w1h' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(issuable.reload.human_time_estimate).to eq('3w 1h') end end @@ -54,7 +54,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "resets the time estimate for #{issuable_name}" do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_time_estimate", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['time_estimate']).to eq(0) end end @@ -73,7 +73,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '2h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['human_total_time_spent']).to eq('2h') end @@ -84,7 +84,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['total_time_spent']).to eq(3600) end end @@ -96,7 +96,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1w' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/) end end @@ -112,7 +112,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "resets spent time for #{issuable_name}" do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(0) end end @@ -124,7 +124,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(1800) expect(json_response['time_estimate']).to eq(3600) end diff --git a/spec/support/api/v3/time_tracking_shared_examples.rb b/spec/support/api/v3/time_tracking_shared_examples.rb index f982b10d999..afe0f4cecda 100644 --- a/spec/support/api/v3/time_tracking_shared_examples.rb +++ b/spec/support/api/v3/time_tracking_shared_examples.rb @@ -11,7 +11,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it "sets the time estimate for #{issuable_name}" do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['human_time_estimate']).to eq('1w') end @@ -24,7 +24,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it 'does not modify the original estimate' do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(issuable.reload.human_time_estimate).to eq('1w') end end @@ -33,7 +33,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it 'updates the estimate' do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(issuable.reload.human_time_estimate).to eq('3w 1h') end end @@ -50,7 +50,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it "resets the time estimate for #{issuable_name}" do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['time_estimate']).to eq(0) end end @@ -69,7 +69,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '2h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['human_total_time_spent']).to eq('2h') end @@ -80,7 +80,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['total_time_spent']).to eq(3600) end end @@ -92,7 +92,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1w' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/) end end @@ -108,7 +108,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it "resets spent time for #{issuable_name}" do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(0) end end @@ -120,7 +120,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(1800) expect(json_response['time_estimate']).to eq(3600) end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb index 01aca74274c..ac0c7a9b493 100644 --- a/spec/support/api_helpers.rb +++ b/spec/support/api_helpers.rb @@ -18,21 +18,23 @@ module ApiHelpers # # Returns the relative path to the requested API resource def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil) - "/api/#{version}#{path}" + + full_path = "/api/#{version}#{path}" - # Normalize query string - (path.index('?') ? '' : '?') + + if oauth_access_token + query_string = "access_token=#{oauth_access_token.token}" + elsif personal_access_token + query_string = "private_token=#{personal_access_token.token}" + elsif user + personal_access_token = create(:personal_access_token, user: user) + query_string = "private_token=#{personal_access_token.token}" + end - if personal_access_token.present? - "&private_token=#{personal_access_token.token}" - elsif oauth_access_token.present? - "&access_token=#{oauth_access_token.token}" - # Append private_token if given a User object - elsif user.respond_to?(:private_token) - "&private_token=#{user.private_token}" - else - '' - end + if query_string + full_path << (path.index('?') ? '&' : '?') + full_path << query_string + end + + full_path end # Temporary helper method for simplifying V3 exclusive API specs diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb new file mode 100644 index 00000000000..38d11992dc2 --- /dev/null +++ b/spec/support/bare_repo_operations.rb @@ -0,0 +1,60 @@ +require 'zlib' + +class BareRepoOperations + # The ID of empty tree. + # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 + EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze + + include Gitlab::Popen + + def initialize(path_to_repo) + @path_to_repo = path_to_repo + end + + # Based on https://stackoverflow.com/a/25556917/1856239 + def commit_file(file, dst_path, branch = 'master') + head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID + + execute(['read-tree', '--empty']) + execute(['read-tree', head_id]) + + blob_id = execute(['hash-object', '--stdin', '-w']) do |stdin| + stdin.write(file.read) + end + + execute(['update-index', '--add', '--cacheinfo', '100644', blob_id[0], dst_path]) + + tree_id = execute(['write-tree']) + + commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"] + commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID + commit_id = execute(commit_tree_args) + + execute(['update-ref', "refs/heads/#{branch}", commit_id[0]]) + end + + private + + def execute(args, allow_failure: false) + output, status = popen(base_args + args, nil) do |stdin| + yield stdin if block_given? + end + + unless status.zero? + if allow_failure + return [] + else + raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}" + end + end + + output.split("\n") + end + + def base_args + [ + Gitlab.config.git.bin_path, + "--git-dir=#{@path_to_repo}" + ] + end +end diff --git a/spec/support/board_helpers.rb b/spec/support/board_helpers.rb new file mode 100644 index 00000000000..507d0432d7f --- /dev/null +++ b/spec/support/board_helpers.rb @@ -0,0 +1,16 @@ +module BoardHelpers + def click_card(card) + within card do + first('.card-number').click + end + + wait_for_sidebar + end + + def wait_for_sidebar + # loop until the CSS transition is complete + Timeout.timeout(0.5) do + loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290 + end + end +end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index c45c4a4310d..9f672bc92fc 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,25 +1,25 @@ # rubocop:disable Style/GlobalVars require 'capybara/rails' require 'capybara/rspec' -require 'capybara/poltergeist' require 'capybara-screenshot/rspec' +require 'selenium-webdriver' # Give CI some extra time timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30 -Capybara.javascript_driver = :poltergeist -Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new( - app, - js_errors: true, - timeout: timeout, - window_size: [1366, 768], - url_whitelist: %w[localhost 127.0.0.1], - url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg], - phantomjs_options: [ - '--load-images=yes' - ] +Capybara.javascript_driver = :chrome +Capybara.register_driver :chrome do |app| + extra_args = [] + extra_args << 'headless' unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i + + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: { + 'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args + } ) + + Capybara::Selenium::Driver + .new(app, browser: :chrome, desired_capabilities: capabilities) end Capybara.default_max_wait_time = timeout @@ -27,6 +27,10 @@ Capybara.ignore_hidden_elements = true # Keep only the screenshots generated from the last failing test suite Capybara::Screenshot.prune_strategy = :keep_last_run +# From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326 +Capybara::Screenshot.register_driver(:chrome) do |driver, path| + driver.browser.save_screenshot(path) +end RSpec.configure do |config| config.before(:context, :js) do @@ -37,13 +41,23 @@ RSpec.configure do |config| end config.before(:example, :js) do + session = Capybara.current_session + allow(Gitlab::Application.routes).to receive(:default_url_options).and_return( - host: Capybara.current_session.server.host, - port: Capybara.current_session.server.port, + host: session.server.host, + port: session.server.port, protocol: 'http') + + # reset window size between tests + unless session.current_window.size == [1240, 1400] + session.current_window.resize_to(1240, 1400) rescue nil + end end config.after(:example, :js) do |example| + # prevent localstorage from introducing side effects based on test order + execute_script("localStorage.clear();") + # capybara/rspec already calls Capybara.reset_sessions! in an `after` hook, # but `block_and_wait_for_requests_complete` is called before it so by # calling it explicitely here, we prevent any new requests from being fired diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb index 3eb7bea3227..868233416bf 100644 --- a/spec/support/capybara_helpers.rb +++ b/spec/support/capybara_helpers.rb @@ -38,7 +38,7 @@ module CapybaraHelpers # Simulate a browser restart by clearing the session cookie. def clear_browser_session - page.driver.remove_cookie('_gitlab_session') + page.driver.browser.manage.delete_cookie('_gitlab_session') end end diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index b23d81a226a..a0839eefe6c 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -14,7 +14,7 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do it "updates access token" do token = 'asdfasdf9876' - allow_any_instance_of(Gitlab::GithubImport::Client) + allow_any_instance_of(Gitlab::LegacyGithubImport::Client) .to receive(:user).and_return(true) post :personal_access_token, personal_access_token: token @@ -79,7 +79,7 @@ shared_examples 'a GitHub-ish import controller: GET status' do end it "handles an invalid access token" do - allow_any_instance_of(Gitlab::GithubImport::Client) + allow_any_instance_of(Gitlab::LegacyGithubImport::Client) .to receive(:repos).and_raise(Octokit::Unauthorized) get :status @@ -110,7 +110,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do context "when the repository owner is the provider user" do context "when the provider user and GitLab user's usernames match" do it "takes the current user's namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -122,7 +122,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do let(:provider_username) { "someone_else" } it "takes the current user's namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -149,7 +149,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it "takes the existing namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -161,7 +161,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do it "creates a project using user's namespace" do create(:user, username: other_username) - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -173,14 +173,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do context "when a namespace with the provider user's username doesn't exist" do context "when current user can create namespaces" do it "creates the namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).and_return(double(execute: true)) expect { post :create, target_namespace: provider_repo.name, format: :js }.to change(Namespace, :count).by(1) end it "takes the new namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider) .and_return(double(execute: true)) @@ -194,14 +194,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it "doesn't create the namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).and_return(double(execute: true)) expect { post :create, format: :js }.not_to change(Namespace, :count) end it "takes the current user's namespace" do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -219,7 +219,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it 'takes the selected namespace and name' do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -227,7 +227,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it 'takes the selected name and default namespace' do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -245,7 +245,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it 'takes the selected namespace and name' do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider) .and_return(double(execute: true)) @@ -257,7 +257,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do let(:test_name) { 'test_name' } it 'takes the selected namespace and name' do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: true)) @@ -265,7 +265,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it 'creates the namespaces' do - allow(Gitlab::GithubImport::ProjectCreator) + allow(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: true)) @@ -274,7 +274,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it 'new namespace has the right parent' do - allow(Gitlab::GithubImport::ProjectCreator) + allow(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: true)) @@ -289,7 +289,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } it 'takes the selected namespace and name' do - expect(Gitlab::GithubImport::ProjectCreator) + expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: true)) @@ -297,7 +297,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end it 'creates the namespaces' do - allow(Gitlab::GithubImport::ProjectCreator) + allow(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: true)) diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb new file mode 100644 index 00000000000..224619c899c --- /dev/null +++ b/spec/support/cookie_helper.rb @@ -0,0 +1,17 @@ +# Helper for setting cookies in Selenium/WebDriver +# +module CookieHelper + def set_cookie(name, value, options = {}) + # Selenium driver will not set cookies for a given domain when the browser is at `about:blank`. + # It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive. + visit options.fetch(:path, '/') unless on_a_page? + page.driver.browser.manage.add_cookie(name: name, value: value, **options) + end + + private + + def on_a_page? + current_url = Capybara.current_session.driver.browser.current_url + current_url && current_url != '' && current_url != 'about:blank' && current_url != 'data:,' + end +end diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb index 934b4557ba2..26fd271ce31 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/cycle_analytics_helpers.rb @@ -94,6 +94,7 @@ module CycleAnalyticsHelpers ref: 'master', tag: false, name: 'dummy', + stage: 'dummy', pipeline: dummy_pipeline, protected: false) end diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb index 3e979f2f470..b39052923dd 100644 --- a/spec/support/email_helpers.rb +++ b/spec/support/email_helpers.rb @@ -1,6 +1,6 @@ module EmailHelpers - def sent_to_user?(user, recipients = email_recipients) - recipients.include?(user.notification_email) + def sent_to_user(user, recipients: email_recipients) + recipients.count { |to| to == user.notification_email } end def reset_delivered_emails! @@ -10,17 +10,17 @@ module EmailHelpers def should_only_email(*users, kind: :to) recipients = email_recipients(kind: kind) - users.each { |user| should_email(user, recipients) } + users.each { |user| should_email(user, recipients: recipients) } expect(recipients.count).to eq(users.count) end - def should_email(user, recipients = email_recipients) - expect(sent_to_user?(user, recipients)).to be_truthy + def should_email(user, times: 1, recipients: email_recipients) + expect(sent_to_user(user, recipients: recipients)).to eq(times) end - def should_not_email(user, recipients = email_recipients) - expect(sent_to_user?(user, recipients)).to be_falsey + def should_not_email(user, recipients: email_recipients) + should_email(user, times: 0, recipients: recipients) end def should_not_email_anyone diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 9f05cabf7ae..c24940393f9 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -71,26 +71,28 @@ shared_examples 'discussion comments' do |resource_name| expect(page).not_to have_selector menu_selector find(toggle_selector).click - find('body').trigger 'click' + execute_script("document.querySelector('body').click()") expect(page).not_to have_selector menu_selector end it 'clicking the ul padding or divider should not change the text' do - find(menu_selector).trigger 'click' + execute_script("document.querySelector('#{menu_selector}').click()") + # on issues page, the menu closes when clicking anywhere, on other pages it will + # remain open if clicking divider or menu padding, but should not change button action if resource_name == 'issue' expect(find(dropdown_selector)).to have_content 'Comment' find(toggle_selector).click - find("#{menu_selector} .divider").trigger 'click' + execute_script("document.querySelector('#{menu_selector} .divider').click()") else - find(menu_selector).trigger 'click' + execute_script("document.querySelector('#{menu_selector}').click()") expect(page).to have_selector menu_selector expect(find(dropdown_selector)).to have_content 'Comment' - find("#{menu_selector} .divider").trigger 'click' + execute_script("document.querySelector('#{menu_selector} .divider').click()") expect(page).to have_selector menu_selector end @@ -105,7 +107,12 @@ shared_examples 'discussion comments' do |resource_name| end it 'updates the submit button text and closes the dropdown' do - expect(find(dropdown_selector)).to have_content 'Start discussion' + # on issues page, the submit input is a <button>, on other pages it is <input> + if resource_name == 'issue' + expect(find(submit_selector)).to have_content 'Start discussion' + else + expect(find(submit_selector).value).to eq 'Start discussion' + end expect(page).not_to have_selector menu_selector end @@ -121,14 +128,31 @@ shared_examples 'discussion comments' do |resource_name| end end - it 'clicking "Start discussion" will post a discussion' do - find(submit_selector).click + describe 'creating a discussion' do + before do + find(submit_selector).click + find(comments_selector, match: :first) + end + + it 'clicking "Start discussion" will post a discussion' do + new_comment = all(comments_selector).last + + expect(new_comment).to have_content 'a' + expect(new_comment).to have_selector '.discussion' + end + + if resource_name == 'merge request' + it 'shows resolved discussion when toggled' do + click_button "Resolve discussion" + + expect(page).to have_selector('.note-row-1', visible: true) - find(comments_selector, match: :first) - new_comment = all(comments_selector).last + refresh + click_button "Toggle discussion" - expect(new_comment).to have_content 'a' - expect(new_comment).to have_selector '.discussion' + expect(page).to have_selector('.note-row-1', visible: true) + end + end end if resource_name == 'issue' @@ -170,7 +194,12 @@ shared_examples 'discussion comments' do |resource_name| end it 'updates the submit button text and closes the dropdown' do - expect(find(dropdown_selector)).to have_content 'Comment' + # on issues page, the submit input is a <button>, on other pages it is <input> + if resource_name == 'issue' + expect(find(submit_selector)).to have_content 'Comment' + else + expect(find(submit_selector).value).to eq 'Comment' + end expect(page).not_to have_selector menu_selector end @@ -209,6 +238,7 @@ shared_examples 'discussion comments' do |resource_name| describe "on a closed #{resource_name}" do before do find("#{form_selector} .js-note-target-close").click + wait_for_requests find("#{form_selector} .note-textarea").send_keys('a') end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 8282ba7e536..08e21ee2537 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -29,7 +29,7 @@ shared_examples 'issuable record that supports quick actions in its description wait_for_requests end - describe "new #{issuable_type}", js: true do + 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 @@ -53,7 +53,7 @@ shared_examples 'issuable record that supports quick actions in its description end end - describe "note on #{issuable_type}", js: true do + describe "note on #{issuable_type}", :js do before do visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -61,7 +61,7 @@ shared_examples 'issuable record that supports quick actions in its description 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') - write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") + write_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' @@ -82,7 +82,7 @@ shared_examples 'issuable record that supports quick actions in its description 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') - write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") + write_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' @@ -290,7 +290,7 @@ shared_examples 'issuable record that supports quick actions in its description end end - describe "preview of note on #{issuable_type}", js: true do + describe "preview of note on #{issuable_type}", :js do it 'removes quick actions from note and explains them' do create(:user, username: 'bob') diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb index 192a2fed0a8..836e5e7be23 100644 --- a/spec/support/features/reportable_note_shared_examples.rb +++ b/spec/support/features/reportable_note_shared_examples.rb @@ -39,7 +39,7 @@ shared_examples 'reportable note' do |type| end def open_dropdown(dropdown) - dropdown.find('.more-actions-toggle').trigger('click') + dropdown.find('.more-actions-toggle').click dropdown.find('.dropdown-menu li', match: :first) end end diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb index 5515c355cea..128aaaf25fe 100644 --- a/spec/support/fixture_helpers.rb +++ b/spec/support/fixture_helpers.rb @@ -1,6 +1,7 @@ module FixtureHelpers def fixture_file(filename) return '' if filename.blank? + File.read(expand_fixture_path(filename)) end diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb index ef3c8e7087f..4ee33f9725b 100755 --- a/spec/support/generate-seed-repo-rb +++ b/spec/support/generate-seed-repo-rb @@ -33,6 +33,7 @@ end def capture!(cmd, dir) output = IO.popen(cmd, 'r', chdir: dir) { |io| io.read } raise "command failed with #{$?}: #{cmd.join(' ')}" unless $?.success? + output.chomp end diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb index 89fb362cf14..c7e8a39a617 100644 --- a/spec/support/gitaly.rb +++ b/spec/support/gitaly.rb @@ -1,6 +1,11 @@ RSpec.configure do |config| config.before(:each) do |example| - next if example.metadata[:skip_gitaly_mock] - allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true) + if example.metadata[:disable_gitaly] + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) + else + next if example.metadata[:skip_gitaly_mock] + + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true) + end end end diff --git a/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 b/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 Binary files differnew file mode 100644 index 00000000000..1c47f34b9a5 --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 diff --git a/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf b/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf Binary files differnew file mode 100644 index 00000000000..ca13c8df66a --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf diff --git a/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 b/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 new file mode 100644 index 00000000000..3be244dbda4 --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 @@ -0,0 +1,2 @@ +x¥ŽK +Â0EgNIÒ|ADtè*^’
mZ qGîÄY×àð8—×ZK©ý®7"ÈFc’Ò%oH¢D²Ü9rZÛLÎs“MJ2Œ™=±ÑÒAå…CmeFg²·V¨xI9øH2†¯þXÜJ…ár»pÅ6‡Ï;NÔà8•zˆ??>ß+–ù×z¡¹WÆBÞÎÙf·Ç}«þßb¡N@K\SYîì•iSC
\ No newline at end of file diff --git a/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e b/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e Binary files differnew file mode 100644 index 00000000000..2bf27fe5048 --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e diff --git a/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f b/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f Binary files differnew file mode 100644 index 00000000000..8ab8606c6be --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json index 688175369ae..658ff5871b0 100644 --- a/spec/support/gitlab_stubs/session.json +++ b/spec/support/gitlab_stubs/session.json @@ -14,7 +14,5 @@ "provider":null, "is_admin":false, "can_create_group":false, - "can_create_project":false, - "private_token":"Wvjy2Krpb7y8xi93owUz", - "access_token":"Wvjy2Krpb7y8xi93owUz" + "can_create_project":false } diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json index ce8dfe5ae75..658ff5871b0 100644 --- a/spec/support/gitlab_stubs/user.json +++ b/spec/support/gitlab_stubs/user.json @@ -14,7 +14,5 @@ "provider":null, "is_admin":false, "can_create_group":false, - "can_create_project":false, - "private_token":"Wvjy2Krpb7y8xi93owUz", - "access_token":"Wvjy2Krpb7y8xi93owUz" -}
\ No newline at end of file + "can_create_project":false +} diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb new file mode 100644 index 00000000000..dabf0db7666 --- /dev/null +++ b/spec/support/google_api/cloud_platform_helpers.rb @@ -0,0 +1,119 @@ +module GoogleApi + module CloudPlatformHelpers + def stub_google_api_validate_token + request.session[GoogleApi::CloudPlatform::Client.session_key_for_token] = 'token' + request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.since.to_i.to_s + end + + def stub_google_api_expired_token + request.session[GoogleApi::CloudPlatform::Client.session_key_for_token] = 'token' + request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.ago.to_i.to_s + end + + def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options) + WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)) + .to_return(cloud_platform_response(cloud_platform_cluster_body(options))) + end + + def stub_cloud_platform_get_zone_cluster_error(project_id, zone, cluster_id) + WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)) + .to_return(status: [500, "Internal Server Error"]) + end + + def stub_cloud_platform_create_cluster(project_id, zone, **options) + WebMock.stub_request(:post, cloud_platform_create_cluster_url(project_id, zone)) + .to_return(cloud_platform_response(cloud_platform_operation_body(options))) + end + + def stub_cloud_platform_create_cluster_error(project_id, zone) + WebMock.stub_request(:post, cloud_platform_create_cluster_url(project_id, zone)) + .to_return(status: [500, "Internal Server Error"]) + end + + def stub_cloud_platform_get_zone_operation(project_id, zone, operation_id, **options) + WebMock.stub_request(:get, cloud_platform_get_zone_operation_url(project_id, zone, operation_id)) + .to_return(cloud_platform_response(cloud_platform_operation_body(options))) + end + + def stub_cloud_platform_get_zone_operation_error(project_id, zone, operation_id) + WebMock.stub_request(:get, cloud_platform_get_zone_operation_url(project_id, zone, operation_id)) + .to_return(status: [500, "Internal Server Error"]) + end + + def cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id) + "https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters/#{cluster_id}" + end + + def cloud_platform_create_cluster_url(project_id, zone) + "https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters" + end + + def cloud_platform_get_zone_operation_url(project_id, zone, operation_id) + "https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/operations/#{operation_id}" + end + + def cloud_platform_response(body) + { status: 200, headers: { 'Content-Type' => 'application/json' }, body: body.to_json } + end + + def load_sample_cert + pem_file = File.expand_path(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) + Base64.encode64(File.read(pem_file)) + end + + ## + # gcloud container clusters create + # https://cloud.google.com/container-engine/reference/rest/v1/projects.zones.clusters/create + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def cloud_platform_cluster_body(**options) + { + "name": options[:name] || 'string', + "description": options[:description] || 'string', + "initialNodeCount": options[:initialNodeCount] || 'number', + "masterAuth": { + "username": options[:username] || 'string', + "password": options[:password] || 'string', + "clusterCaCertificate": options[:clusterCaCertificate] || load_sample_cert, + "clientCertificate": options[:clientCertificate] || 'string', + "clientKey": options[:clientKey] || 'string' + }, + "loggingService": options[:loggingService] || 'string', + "monitoringService": options[:monitoringService] || 'string', + "network": options[:network] || 'string', + "clusterIpv4Cidr": options[:clusterIpv4Cidr] || 'string', + "subnetwork": options[:subnetwork] || 'string', + "enableKubernetesAlpha": options[:enableKubernetesAlpha] || 'boolean', + "labelFingerprint": options[:labelFingerprint] || 'string', + "selfLink": options[:selfLink] || 'string', + "zone": options[:zone] || 'string', + "endpoint": options[:endpoint] || 'string', + "initialClusterVersion": options[:initialClusterVersion] || 'string', + "currentMasterVersion": options[:currentMasterVersion] || 'string', + "currentNodeVersion": options[:currentNodeVersion] || 'string', + "createTime": options[:createTime] || 'string', + "status": options[:status] || 'RUNNING', + "statusMessage": options[:statusMessage] || 'string', + "nodeIpv4CidrSize": options[:nodeIpv4CidrSize] || 'number', + "servicesIpv4Cidr": options[:servicesIpv4Cidr] || 'string', + "currentNodeCount": options[:currentNodeCount] || 'number', + "expireTime": options[:expireTime] || 'string' + } + end + + def cloud_platform_operation_body(**options) + { + "name": options[:name] || 'operation-1234567891234-1234567', + "zone": options[:zone] || 'us-central1-a', + "operationType": options[:operationType] || 'CREATE_CLUSTER', + "status": options[:status] || 'PENDING', + "detail": options[:detail] || 'detail', + "statusMessage": options[:statusMessage] || '', + "selfLink": options[:selfLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/operations/operation-1234567891234-1234567', + "targetLink": options[:targetLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/clusters/test-cluster', + "startTime": options[:startTime] || '2017-09-13T16:49:13.055601589Z', + "endTime": options[:endTime] || '' + } + end + end +end diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb index fd22e384b1b..c98aa503ed1 100644 --- a/spec/support/helpers/merge_request_diff_helpers.rb +++ b/spec/support/helpers/merge_request_diff_helpers.rb @@ -2,7 +2,7 @@ module MergeRequestDiffHelpers def click_diff_line(line_holder, diff_side = nil) line = get_line_components(line_holder, diff_side) line[:content].hover - line[:num].find('.add-diff-note').trigger('click') + line[:num].find('.add-diff-note', visible: false).send_keys(:return) end def get_line_components(line_holder, diff_side = nil) diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb index 86008698692..79a0aa174b1 100644 --- a/spec/support/helpers/note_interaction_helpers.rb +++ b/spec/support/helpers/note_interaction_helpers.rb @@ -2,7 +2,7 @@ module NoteInteractionHelpers def open_more_actions_dropdown(note) note_element = find("#note_#{note.id}") - note_element.find('.more-actions-toggle').trigger('click') + note_element.find('.more-actions-toggle').click note_element.find('.more-actions .dropdown-menu li', match: :first) end end diff --git a/spec/support/input_helper.rb b/spec/support/input_helper.rb new file mode 100644 index 00000000000..acbb42274ec --- /dev/null +++ b/spec/support/input_helper.rb @@ -0,0 +1,7 @@ +# see app/assets/javascripts/test_utils/simulate_input.js + +module InputHelper + def simulate_input(selector, input = '') + evaluate_script("window.simulateInput(#{selector.to_json}, #{input.to_json});") + end +end diff --git a/spec/support/inspect_requests.rb b/spec/support/inspect_requests.rb new file mode 100644 index 00000000000..88ddc5c7f6c --- /dev/null +++ b/spec/support/inspect_requests.rb @@ -0,0 +1,17 @@ +require_relative './wait_for_requests' + +module InspectRequests + extend self + include WaitForRequests + + def inspect_requests(inject_headers: {}) + Gitlab::Testing::RequestInspectorMiddleware.log_requests!(inject_headers) + + yield + + wait_for_all_requests + Gitlab::Testing::RequestInspectorMiddleware.requests + ensure + Gitlab::Testing::RequestInspectorMiddleware.stop_logging! + end +end diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb index 0b5f66597fd..88a7aeba461 100644 --- a/spec/support/jira_service_helper.rb +++ b/spec/support/jira_service_helper.rb @@ -6,6 +6,8 @@ module JiraServiceHelper properties = { title: "JIRA tracker", url: JIRA_URL, + username: 'jira-user', + password: 'my-secret-password', project_key: "JIRA", jira_issue_transition_id: '1' } diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb index c92f78b324c..e46b61b6461 100644 --- a/spec/support/kubernetes_helpers.rb +++ b/spec/support/kubernetes_helpers.rb @@ -9,22 +9,51 @@ module KubernetesHelpers kube_response(kube_pods_body) end - def stub_kubeclient_discover - WebMock.stub_request(:get, service.api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body)) + def stub_kubeclient_discover(api_url) + WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body)) end def stub_kubeclient_pods(response = nil) - stub_kubeclient_discover + stub_kubeclient_discover(service.api_url) pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) end + def stub_kubeclient_get_secrets(api_url, **options) + WebMock.stub_request(:get, api_url + '/api/v1/secrets') + .to_return(kube_response(kube_v1_secrets_body(options))) + end + + def stub_kubeclient_get_secrets_error(api_url) + WebMock.stub_request(:get, api_url + '/api/v1/secrets') + .to_return(status: [404, "Internal Server Error"]) + end + + def kube_v1_secrets_body(**options) + { + "kind" => "SecretList", + "apiVersion": "v1", + "items" => [ + { + "metadata": { + "name": options[:metadata_name] || "default-token-1", + "namespace": "kube-system" + }, + "data": { + "token": options[:token] || Base64.encode64('token-sample-123') + } + } + ] + } + end + def kube_v1_discovery_body { "kind" => "APIResourceList", "resources" => [ - { "name" => "pods", "namespaced" => true, "kind" => "Pod" } + { "name" => "pods", "namespaced" => true, "kind" => "Pod" }, + { "name" => "secrets", "namespaced" => true, "kind" => "Secret" } ] } end diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb index 079f244475c..28d39a32f02 100644 --- a/spec/support/ldap_helpers.rb +++ b/spec/support/ldap_helpers.rb @@ -15,10 +15,7 @@ module LdapHelpers # admin_group: 'my-admin-group' # ) def stub_ldap_config(messages) - messages.each do |config, value| - allow_any_instance_of(::Gitlab::LDAP::Config) - .to receive(config.to_sym).and_return(value) - end + allow_any_instance_of(::Gitlab::LDAP::Config).to receive_messages(messages) end # Stub an LDAP person search and provide the return entry. Specify `nil` for diff --git a/spec/support/legacy_path_redirect_shared_examples.rb b/spec/support/legacy_path_redirect_shared_examples.rb new file mode 100644 index 00000000000..f300bdd48b1 --- /dev/null +++ b/spec/support/legacy_path_redirect_shared_examples.rb @@ -0,0 +1,13 @@ +shared_examples 'redirecting a legacy path' do |source, target| + include RSpec::Rails::RequestExampleGroup + + it "redirects #{source} to #{target} when the resource does not exist" do + expect(get(source)).to redirect_to(target) + end + + it "does not redirect #{source} to #{target} when the resource exists" do + resource + + expect(get(source)).not_to redirect_to(target) + end +end diff --git a/spec/support/live_debugger.rb b/spec/support/live_debugger.rb new file mode 100644 index 00000000000..911eb48a8ca --- /dev/null +++ b/spec/support/live_debugger.rb @@ -0,0 +1,17 @@ +require 'io/console' + +module LiveDebugger + def live_debug + puts + puts "Current example is paused for live debugging." + puts "Opening #{current_url} in your default browser..." + puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user + puts "Press any key to resume the execution of the example!!" + + `open #{current_url}` + + loop until $stdin.getch + + puts "Back to the example!" + end +end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 3e117530151..50702a0ac88 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -3,6 +3,21 @@ require_relative 'devise_helpers' module LoginHelpers include DeviseHelpers + # Overriding Devise::Test::IntegrationHelpers#sign_in to store @current_user + # since we may need it in LiveDebugger#live_debug. + def sign_in(resource, scope: nil) + super + + @current_user = resource + end + + # Overriding Devise::Test::IntegrationHelpers#sign_out to clear @current_user. + def sign_out(resource_or_scope) + super + + @current_user = nil + end + # Internal: Log in as a specific user or a new user of a specific role # # user_or_role - User object, or a role to create (e.g., :admin, :user) @@ -28,7 +43,7 @@ module LoginHelpers gitlab_sign_in_with(user, **kwargs) - user + @current_user = user end def gitlab_sign_in_via(provider, user, uid) @@ -41,6 +56,7 @@ module LoginHelpers def gitlab_sign_out find(".header-user-dropdown-toggle").click click_link "Sign out" + @current_user = nil expect(page).to have_button('Sign in') end @@ -120,4 +136,16 @@ module LoginHelpers allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') end + + def stub_omniauth_config(messages) + allow(Gitlab.config.omniauth).to receive_messages(messages) + end + + def stub_basic_saml_config + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) + end + + def stub_saml_group_config(groups) + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) + end end diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index bb6b7c63ee9..cdb62a5deee 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -5,7 +5,7 @@ module AccessMatchersForController extend RSpec::Matchers::DSL include Warden::Test::Helpers - EXPECTED_STATUS_CODE_ALLOWED = [200, 201, 302].freeze + EXPECTED_STATUS_CODE_ALLOWED = [200, 201, 204, 302].freeze EXPECTED_STATUS_CODE_DENIED = [401, 404].freeze def emulate_user(role, membership = nil) diff --git a/spec/support/matchers/be_a_binary_string.rb b/spec/support/matchers/be_a_binary_string.rb new file mode 100644 index 00000000000..f041ae76167 --- /dev/null +++ b/spec/support/matchers/be_a_binary_string.rb @@ -0,0 +1,9 @@ +RSpec::Matchers.define :be_a_binary_string do |_| + match do |actual| + actual.is_a?(String) && actual.encoding == Encoding.find('ASCII-8BIT') + end + + description do + "be a String with binary encoding" + end +end diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb index 3198f1b9edd..e7e418cdde4 100644 --- a/spec/support/matchers/have_gitlab_http_status.rb +++ b/spec/support/matchers/have_gitlab_http_status.rb @@ -8,7 +8,11 @@ RSpec::Matchers.define :have_gitlab_http_status do |expected| end failure_message do |actual| + # actual can be either an ActionDispatch::TestResponse (which uses #response_code) + # or a Capybara::Session (which uses #status_code) + response_code = actual.try(:response_code) || actual.try(:status_code) + "expected the response to have status code #{expected.inspect}" \ - " but it was #{actual.response_code}. The response was: #{actual.body}" + " but it was #{response_code}. The response was: #{actual.body}" end end diff --git a/spec/support/matchers/security_header_matcher.rb b/spec/support/matchers/security_header_matcher.rb new file mode 100644 index 00000000000..f8518d13ebb --- /dev/null +++ b/spec/support/matchers/security_header_matcher.rb @@ -0,0 +1,5 @@ +RSpec::Matchers.define :include_security_headers do |expected| + match do |actual| + expect(actual.headers).to include('X-Content-Type-Options') + end +end diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb index 431f20a2a5c..3b9eb84e824 100644 --- a/spec/support/mobile_helpers.rb +++ b/spec/support/mobile_helpers.rb @@ -12,6 +12,6 @@ module MobileHelpers end def resize_window(width, height) - page.driver.resize_window width, height + Capybara.current_session.current_window.resize_to(width, height) end end diff --git a/spec/support/project_forks_helper.rb b/spec/support/project_forks_helper.rb index 0d1c6792d13..d6680735aa1 100644 --- a/spec/support/project_forks_helper.rb +++ b/spec/support/project_forks_helper.rb @@ -52,7 +52,7 @@ module ProjectForksHelper 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 diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 620fa37d455..dbbd4ad4d40 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -41,16 +41,30 @@ RSpec.shared_examples 'additional metrics query' do end describe 'project has Kubernetes service' do - let(:project) { create(:kubernetes_project) } - let(:environment) { create(:environment, slug: 'environment-slug', project: project) } - let(:kube_namespace) { project.kubernetes_service.actual_namespace } + 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 } - it_behaves_like 'query context containing environment slug and filter' + it_behaves_like 'query context containing environment slug and filter' - it 'query context contains kube_namespace' do - expect(subject).to receive(:query_metrics).with(hash_including(kube_namespace: kube_namespace)) + it 'query context contains kube_namespace' do + expect(subject).to receive(:query_metrics).with(hash_including(kube_namespace: kube_namespace)) - subject.query(*query_params) + subject.query(*query_params) + end + end + + context 'when user configured kubernetes from Integration > Kubernetes' do + let(:project) { create(:kubernetes_project) } + + it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' + end + + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + + it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' end end diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb index 421a51fc336..71eec9f3217 100644 --- a/spec/support/protected_tags/access_control_ce_shared_examples.rb +++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb @@ -1,5 +1,5 @@ RSpec.shared_examples "protected tags > access control > CE" do - ProtectedTag::CreateAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| it "allows creating protected tags that #{access_type_name} can create" do visit project_protected_tags_path(project) @@ -9,7 +9,7 @@ RSpec.shared_examples "protected tags > access control > CE" do allowed_to_create_button = find(".js-allowed-to-create") unless allowed_to_create_button.text == access_type_name - allowed_to_create_button.trigger('click') + allowed_to_create_button.click find('.create_access_levels-container .dropdown-menu li', match: :first) within('.create_access_levels-container .dropdown-menu') { click_on access_type_name } end diff --git a/spec/support/query_recorder.rb b/spec/support/query_recorder.rb index ba0b805caad..369775db462 100644 --- a/spec/support/query_recorder.rb +++ b/spec/support/query_recorder.rb @@ -8,7 +8,14 @@ module ActiveRecord ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) end + def show_backtrace(values) + Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") + caller.each { |line| Rails.logger.debug(" --> #{line}") } + end + def callback(name, start, finish, message_id, values) + show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG'] + if values[:name]&.include?("CACHE") @cached << values[:sql] elsif !values[:name]&.include?("SCHEMA") @@ -69,10 +76,17 @@ RSpec::Matchers.define :exceed_query_limit do |expected| @recorder.count end + def count_queries(queries) + queries.each_with_object(Hash.new(0)) { |query, counts| counts[query] += 1 } + end + def log_message if expected.is_a?(ActiveRecord::QueryRecorder) - extra_queries = (expected.log - @recorder.log).join("\n\n") - "Extra queries: \n\n #{extra_queries}" + counts = count_queries(expected.log) + extra_queries = @recorder.log.reject { |query| counts[query] -= 1 unless counts[query].zero? } + extra_queries_display = count_queries(extra_queries).map { |query, count| "[#{count}] #{query}" } + + (['Extra queries:'] + extra_queries_display).join("\n\n") else @recorder.log_message end diff --git a/spec/support/quick_actions_helpers.rb b/spec/support/quick_actions_helpers.rb index d2aaae7518f..361190aa352 100644 --- a/spec/support/quick_actions_helpers.rb +++ b/spec/support/quick_actions_helpers.rb @@ -3,7 +3,7 @@ module QuickActionsHelpers Sidekiq::Testing.fake! do page.within('.js-main-target-form') do fill_in 'note[note]', with: text - find('.js-comment-submit-button').trigger('click') + find('.js-comment-submit-button').click end end end diff --git a/spec/support/redis_without_keys.rb b/spec/support/redis_without_keys.rb new file mode 100644 index 00000000000..6220167dee6 --- /dev/null +++ b/spec/support/redis_without_keys.rb @@ -0,0 +1,8 @@ +class Redis + ForbiddenCommand = Class.new(StandardError) + + def keys(*args) + raise ForbiddenCommand.new("Don't use `Redis#keys` as it iterates over all "\ + "keys in redis. Use `Redis#scan_each` instead.") + end +end diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb index 6b1853c2364..55da961e173 100644 --- a/spec/support/select2_helper.rb +++ b/spec/support/select2_helper.rb @@ -16,6 +16,7 @@ module Select2Helper selector = options.fetch(:from) + first(selector, visible: false) if options[:multiple] execute_script("$('#{selector}').select2('val', ['#{value}']).trigger('change');") else diff --git a/spec/support/selection_helper.rb b/spec/support/selection_helper.rb new file mode 100644 index 00000000000..b4725b137b2 --- /dev/null +++ b/spec/support/selection_helper.rb @@ -0,0 +1,6 @@ +module SelectionHelper + def select_element(selector) + find(selector) + execute_script("let range = document.createRange(); let sel = window.getSelection(); range.selectNodeContents(document.querySelector('#{selector}')); sel.addRange(range);") + end +end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index d5bc12f3bc5..17f319f49e9 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -1,5 +1,5 @@ shared_examples "protected branches > access control > CE" do - ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do visit project_protected_branches_path(project) @@ -9,7 +9,7 @@ shared_examples "protected branches > access control > CE" do allowed_to_push_button = find(".js-allowed-to-push") unless allowed_to_push_button.text == access_type_name - allowed_to_push_button.trigger('click') + allowed_to_push_button.click within(".dropdown.open .dropdown-menu") { click_on access_type_name } end end @@ -34,7 +34,7 @@ shared_examples "protected branches > access control > CE" do within('.js-allowed-to-push-container') do expect(first("li")).to have_content("Roles") - click_on access_type_name + find(:link, access_type_name).click end end @@ -44,7 +44,7 @@ shared_examples "protected branches > access control > CE" do end end - ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can merge to" do visit project_protected_branches_path(project) @@ -79,7 +79,7 @@ shared_examples "protected branches > access control > CE" do within('.js-allowed-to-merge-container') do expect(first("li")).to have_content("Roles") - click_on access_type_name + find(:link, access_type_name).click end end diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb new file mode 100644 index 00000000000..a4762b68858 --- /dev/null +++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb @@ -0,0 +1,57 @@ +# This shared example requires a `builder` and `user` variable +shared_examples 'issuable hook data' do |kind| + let(:data) { builder.build(user: user) } + + include_examples 'project hook data' do + let(:project) { builder.issuable.project } + end + include_examples 'deprecated repository hook data' + + context "with a #{kind}" do + it 'contains issuable data' do + expect(data[:object_kind]).to eq(kind) + expect(data[:user]).to eq(user.hook_attrs) + expect(data[:project]).to eq(builder.issuable.project.hook_attrs) + expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) + expect(data[:changes]).to eq({}) + expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) + end + + it 'does not contain certain keys' do + expect(data).not_to have_key(:assignees) + expect(data).not_to have_key(:assignee) + end + + describe 'changes are given' do + let(:changes) do + { + cached_markdown_version: %w[foo bar], + description: ['A description', 'A cool description'], + description_html: %w[foo bar], + in_progress_merge_commit_sha: %w[foo bar], + lock_version: %w[foo bar], + merge_jid: %w[foo bar], + title: ['A title', 'Hello World'], + title_html: %w[foo bar] + } + end + let(:data) { builder.build(user: user, changes: changes) } + + it 'populates the :changes hash' do + expect(data[:changes]).to match(hash_including({ + title: { previous: 'A title', current: 'Hello World' }, + description: { previous: 'A description', current: 'A cool description' } + })) + end + + it 'does not contain certain keys' do + expect(data[:changes]).not_to have_key('cached_markdown_version') + expect(data[:changes]).not_to have_key('description_html') + expect(data[:changes]).not_to have_key('lock_version') + expect(data[:changes]).not_to have_key('title_html') + expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha') + expect(data[:changes]).not_to have_key('merge_jid') + end + end + end +end diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb index 1eb405d4be8..f0264878811 100644 --- a/spec/support/project_hook_data_shared_example.rb +++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb @@ -1,4 +1,4 @@ -RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :project| +shared_examples 'project hook data with deprecateds' do |project_key: :project| it 'contains project data' do expect(data[project_key][:name]).to eq(project.name) expect(data[project_key][:description]).to eq(project.description) @@ -17,7 +17,7 @@ RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :pro end end -RSpec.shared_examples 'project hook data' do |project_key: :project| +shared_examples 'project hook data' do |project_key: :project| it 'contains project data' do expect(data[project_key][:name]).to eq(project.name) expect(data[project_key][:description]).to eq(project.description) @@ -32,7 +32,7 @@ RSpec.shared_examples 'project hook data' do |project_key: :project| end end -RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project| +shared_examples 'deprecated repository hook data' do it 'contains deprecated repository data' do expect(data[:repository][:name]).to eq(project.name) expect(data[:repository][:description]).to eq(project.description) diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index c9302f7b750..4e18804b937 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -3,21 +3,24 @@ shared_examples 'custom attributes endpoints' do |attributable_name| let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' } describe "GET /#{attributable_name} with custom attributes filter" do - let!(:other_attributable) { create attributable.class.name.underscore } + before do + other_attributable + end context 'with an unauthorized user' do it 'does not filter by custom attributes' do get api("/#{attributable_name}", user), custom_attributes: { foo: 'foo', bar: 'bar' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to be 2 + expect(json_response.map { |r| r['id'] }).to contain_exactly attributable.id, other_attributable.id end end it 'filters by custom attributes' do get api("/#{attributable_name}", admin), custom_attributes: { foo: 'foo', bar: 'bar' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to be 1 expect(json_response.first['id']).to eq attributable.id end @@ -33,7 +36,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it 'returns all custom attributes' do get api("/#{attributable_name}/#{attributable.id}/custom_attributes", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to contain_exactly( { 'key' => 'foo', 'value' => 'foo' }, { 'key' => 'bar', 'value' => 'bar' } @@ -51,7 +54,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it 'returns a single custom attribute' do get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq({ 'key' => 'foo', 'value' => 'foo' }) end end @@ -68,7 +71,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), value: 'new' end.to change { attributable.custom_attributes.count }.by(1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq({ 'key' => 'new', 'value' => 'new' }) expect(attributable.custom_attributes.find_by(key: 'new').value).to eq 'new' end @@ -78,7 +81,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), value: 'new' end.not_to change { attributable.custom_attributes.count } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq({ 'key' => 'foo', 'value' => 'new' }) expect(custom_attribute1.reload.value).to eq 'new' end @@ -96,7 +99,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) end.to change { attributable.custom_attributes.count }.by(-1) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(attributable.custom_attributes.find_by(key: 'foo')).to be_nil end end diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb index 7d7f66adeab..0ed917e448a 100644 --- a/spec/support/shared_examples/requests/api/status_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb @@ -3,6 +3,8 @@ # Requires an API request: # let(:request) { get api("/projects/#{project.id}/repository/branches", user) } shared_examples_for '400 response' do + let(:message) { nil } + before do # Fires the request request @@ -10,6 +12,10 @@ shared_examples_for '400 response' do it 'returns 400' do expect(response).to have_gitlab_http_status(400) + + if message.present? + expect(json_response['message']).to eq(message) + end end end @@ -26,6 +32,7 @@ end shared_examples_for '404 response' do let(:message) { nil } + before do # Fires the request request diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 6accf16bea4..17f3a861ba8 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -76,8 +76,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do message: "user created page: Awesome wiki_page" } - wiki_page_service = WikiPages::CreateService.new(project, user, opts) - @wiki_page = wiki_page_service.execute + @wiki_page = create(:wiki_page, wiki: project.wiki, attrs: opts) @wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create') end diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index 2dfb4d4a07f..4ead78529c3 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -38,15 +38,15 @@ module StubConfiguration allow(Gitlab.config.backup).to receive_messages(to_settings(messages)) end + def stub_lfs_setting(messages) + allow(Gitlab.config.lfs).to receive_messages(to_settings(messages)) + end + def stub_storage_settings(messages) # Default storage is always required messages['default'] ||= Gitlab.config.repositories.storages.default messages.each do |storage_name, storage_settings| storage_settings['path'] = TestEnv.repos_path unless storage_settings.key?('path') - storage_settings['failure_count_threshold'] ||= 10 - storage_settings['failure_wait_time'] ||= 30 - storage_settings['failure_reset_time'] ||= 1800 - storage_settings['storage_timeout'] ||= 5 end allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index a27bfdee3d2..fff120fcb88 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -182,6 +182,8 @@ module TestEnv return unless @gitaly_pid Process.kill('KILL', @gitaly_pid) + rescue Errno::ESRCH + # The process can already be gone if the test run was INTerrupted. end def setup_factory_repo diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb index 0fa74f911f6..909d4e2ee8d 100644 --- a/spec/support/time_tracking_shared_examples.rb +++ b/spec/support/time_tracking_shared_examples.rb @@ -80,6 +80,6 @@ end def submit_time(quick_action) fill_in 'note[note]', with: quick_action - find('.js-comment-submit-button').trigger('click') + find('.js-comment-submit-button').click wait_for_requests end diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb index 2dfa5fbecea..3d9705c9c05 100644 --- a/spec/support/unique_ip_check_shared_examples.rb +++ b/spec/support/unique_ip_check_shared_examples.rb @@ -56,13 +56,13 @@ shared_examples 'user login request with unique ip limit' do |success_status = 2 end it 'allows user authenticating from the same ip' do - expect(request_from_ip('ip')).to have_http_status(success_status) - expect(request_from_ip('ip')).to have_http_status(success_status) + expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) + expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) end it 'blocks user authenticating from two distinct ips' do - expect(request_from_ip('ip')).to have_http_status(success_status) - expect(request_from_ip('ip2')).to have_http_status(403) + expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) + expect(request_from_ip('ip2')).to have_gitlab_http_status(403) end end end diff --git a/spec/support/update_invalid_issuable.rb b/spec/support/update_invalid_issuable.rb index 50a1d4a56e2..1490287681b 100644 --- a/spec/support/update_invalid_issuable.rb +++ b/spec/support/update_invalid_issuable.rb @@ -25,13 +25,11 @@ shared_examples 'update invalid issuable' do |klass| .and_raise(ActiveRecord::StaleObjectError.new(issuable, :save)) end - if klass == MergeRequest - it 'renders edit when format is html' do - put :update, params + it 'renders edit when format is html' do + put :update, params - expect(response).to render_template(:edit) - expect(assigns[:conflict]).to be_truthy - end + expect(response).to render_template(:edit) + expect(assigns[:conflict]).to be_truthy end it 'renders json error message when format is json' do @@ -44,17 +42,16 @@ shared_examples 'update invalid issuable' do |klass| end end - if klass == MergeRequest - context 'when updating an invalid issuable' do - before do - params[:merge_request][:title] = "" - end + context 'when updating an invalid issuable' do + before do + key = klass == Issue ? :issue : :merge_request + params[key][:title] = "" + end - it 'renders edit when merge request is invalid' do - put :update, params + it 'renders edit when merge request is invalid' do + put :update, params - expect(response).to render_template(:edit) - end + expect(response).to render_template(:edit) end end end diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb index b5c3c0f55b8..f4130d68271 100644 --- a/spec/support/wait_for_requests.rb +++ b/spec/support/wait_for_requests.rb @@ -1,25 +1,47 @@ -require_relative './wait_for_requests' - module WaitForRequests extend self # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests def block_and_wait_for_requests_complete + block_requests { wait_for_all_requests } + end + + # Block all requests inside block with 503 response + def block_requests Gitlab::Testing::RequestBlockerMiddleware.block_requests! - wait_for('pending requests complete') do - Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? && finished_all_requests? - end + yield ensure Gitlab::Testing::RequestBlockerMiddleware.allow_requests! end + # Slow down requests inside block by injecting `sleep 0.2` before each response + def slow_requests + Gitlab::Testing::RequestBlockerMiddleware.slow_requests! + yield + ensure + Gitlab::Testing::RequestBlockerMiddleware.allow_requests! + end + + # Wait for client-side AJAX requests def wait_for_requests - wait_for('JS requests') { finished_all_requests? } + wait_for('JS requests complete') { finished_all_js_requests? } + end + + # Wait for active Rack requests and client-side AJAX requests + def wait_for_all_requests + wait_for('pending requests complete') do + finished_all_rack_reqiests? && + finished_all_js_requests? + end end private - def finished_all_requests? + def finished_all_rack_reqiests? + Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? + end + + def finished_all_js_requests? return true unless javascript_test? finished_all_ajax_requests? && |