diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-13 18:50:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-13 18:50:28 +0300 |
commit | a21e41126b4430cfdde3ba19fcf29eb1b945967b (patch) | |
tree | 93b355debe75023189f0baaf07a5167c14742115 /qa | |
parent | 96b52080893d23a79c29a9836762b9c135111cc4 (diff) |
Add latest changes from gitlab-org/gitlab@12-0-stable-ee
Diffstat (limited to 'qa')
39 files changed, 2141 insertions, 31 deletions
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb index eea5717f5a7..8d5c30bfdcf 100644 --- a/qa/qa/page/admin/menu.rb +++ b/qa/qa/page/admin/menu.rb @@ -4,6 +4,8 @@ module QA module Page module Admin class Menu < Page::Base + prepend EE::Page::Admin::Menu # rubocop: disable Cop/InjectEnterpriseEditionModule + view 'app/views/layouts/nav/sidebar/_admin.html.haml' do element :admin_sidebar element :admin_sidebar_submenu diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 0c23d7cffbb..81c2a8c33f4 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -4,6 +4,8 @@ module QA module Page module Dashboard class Projects < Page::Base + prepend QA::EE::Page::Dashboard::Projects + view 'app/views/shared/projects/_search_form.html.haml' do element :project_filter_form, required: true end diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb index eaf88c6e69e..9af416d2de7 100644 --- a/qa/qa/page/file/show.rb +++ b/qa/qa/page/file/show.rb @@ -4,6 +4,8 @@ module QA module Page module File class Show < Page::Base + prepend QA::EE::Page::File::Show + include Shared::CommitMessage view 'app/helpers/blob_helper.rb' do diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 8970eeb6678..8d18b32b52f 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -52,13 +52,11 @@ module QA raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given? if Runtime::User.ldap_user? - sign_in_using_ldap_credentials + sign_in_using_ldap_credentials(user || Runtime::User) else sign_in_using_gitlab_credentials(user || Runtime::User) end end - - Page::Main::Menu.act { has_personal_area? } end def sign_in_using_admin_credentials @@ -73,6 +71,25 @@ module QA sign_in_using_gitlab_credentials(admin) end + Page::Main::Menu.perform(&:has_personal_area?) + end + + def sign_in_using_ldap_credentials(user) + # Log out if already logged in + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + + using_wait_time 0 do + set_initial_password_if_present + + switch_to_ldap_tab + + fill_element :username_field, user.ldap_username + fill_element :password_field, user.ldap_password + click_element :sign_in_button + end + Page::Main::Menu.act { has_personal_area? } end @@ -133,14 +150,6 @@ module QA private - def sign_in_using_ldap_credentials - switch_to_ldap_tab - - fill_element :username_field, Runtime::User.ldap_username - fill_element :password_field, Runtime::User.ldap_password - click_element :sign_in_button - end - def sign_in_using_gitlab_credentials(user) switch_to_sign_in_tab if has_sign_in_tab? switch_to_standard_tab if has_standard_tab? diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 6a415b56e50..97c2c616299 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -4,6 +4,7 @@ module QA module Page module MergeRequest class Show < Page::Base + prepend QA::EE::Page::MergeRequest::Show include Page::Component::Note view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do @@ -174,6 +175,12 @@ module QA click_element :edit_button end + def approvers + within_element :approver_list do + all_elements(:approver).map(&:text) + end + end + def view_email_patches click_element :dropdown_toggle visit_link_in_element(:download_email_patches) diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb index 2d503499e13..3386cd6be9d 100644 --- a/qa/qa/page/profile/menu.rb +++ b/qa/qa/page/profile/menu.rb @@ -4,6 +4,8 @@ module QA module Page module Profile class Menu < Page::Base + prepend QA::EE::Page::Profile::Menu + view 'app/views/layouts/nav/sidebar/_profile.html.haml' do element :access_token_link, 'link_to profile_personal_access_tokens_path' # rubocop:disable QA/ElementWithPattern element :access_token_title, 'Access Tokens' # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb index c4383951ec4..bf06d146865 100644 --- a/qa/qa/page/project/issue/index.rb +++ b/qa/qa/page/project/issue/index.rb @@ -5,6 +5,8 @@ module QA module Project module Issue class Index < Page::Base + prepend QA::EE::Page::Project::Issue::Index + view 'app/views/projects/issues/_issue.html.haml' do element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb index 0d138417176..6ff02b771db 100644 --- a/qa/qa/page/project/issue/new.rb +++ b/qa/qa/page/project/issue/new.rb @@ -5,18 +5,18 @@ module QA module Project module Issue class New < Page::Base + view 'app/views/shared/form_elements/_description.html.haml' do + element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description" # rubocop:disable QA/ElementWithPattern + end + view 'app/views/shared/issuable/_form.html.haml' do - element :submit_issue_button, 'form.submit "Submit' # rubocop:disable QA/ElementWithPattern + element :issuable_create_button end view 'app/views/shared/issuable/form/_title.html.haml' do element :issue_title_textbox, 'form.text_field :title' # rubocop:disable QA/ElementWithPattern end - view 'app/views/shared/form_elements/_description.html.haml' do - element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description" # rubocop:disable QA/ElementWithPattern - end - def add_title(title) fill_in 'issue_title', with: title end @@ -26,7 +26,7 @@ module QA end def create_new_issue - click_on 'Submit issue' + click_element :issuable_create_button, Page::Project::Issue::Show end end end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index b59540d0377..805805bc869 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -5,6 +5,7 @@ module QA module Project module Issue class Show < Page::Base + prepend QA::EE::Page::Project::Issue::Show include Page::Component::Issuable::Common include Page::Component::Note @@ -14,7 +15,7 @@ module QA end view 'app/assets/javascripts/notes/components/discussion_filter.vue' do - element :discussion_filter + element :discussion_filter, required: true element :filter_options end @@ -26,17 +27,17 @@ module QA element :dropdown_input_field end + view 'app/views/shared/issuable/_sidebar.html.haml' do + element :dropdown_menu_labels + element :edit_link_labels + element :labels_block + end + view 'app/views/shared/notes/_form.html.haml' do element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern end - view 'app/views/shared/issuable/_sidebar.html.haml' do - element :labels_block - element :edit_link_labels - element :dropdown_menu_labels - end - # Adds a comment to an issue # attachment option should be an absolute path def comment(text, attachment: nil, filter: :all_activities) diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index defd85a5740..a8b9fa56996 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -5,6 +5,7 @@ module QA module Project class New < Page::Base include Page::Component::Select2 + prepend EE::Page::Project::New # rubocop: disable Cop/InjectEnterpriseEditionModule view 'app/views/projects/new.html.haml' do element :project_create_from_template_tab diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 284d0957eb8..cb82b650c57 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -3,6 +3,8 @@ module QA::Page module Project::Pipeline class Show < QA::Page::Base + prepend QA::EE::Page::Project::Pipeline::Show + view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do element :pipeline_header, /header class.*ci-header-container.*/ # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index 831166f6373..fa915851cca 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -5,6 +5,8 @@ module QA module Project module Settings class MirroringRepositories < Page::Base + prepend EE::Page::Project::Settings::MirroringRepositories # rubocop: disable Cop/InjectEnterpriseEditionModule + view 'app/views/projects/mirrors/_authentication_method.html.haml' do element :authentication_method element :password diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 903b0979614..ee8bc223044 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -5,6 +5,8 @@ module QA module Project module Settings class ProtectedBranches < Page::Base + prepend EE::Page::Project::Settings::ProtectedBranches # rubocop: disable Cop/InjectEnterpriseEditionModule + view 'app/views/projects/protected_branches/shared/_dropdown.html.haml' do element :protected_branch_select element :protected_branch_dropdown diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 1a9a2fd413f..3b8493c0a7a 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -4,6 +4,8 @@ module QA module Page module Project class Show < Page::Base + prepend QA::EE::Page::Project::Show + include Page::Component::ClonePanel view 'app/views/layouts/header/_new_dropdown.haml' do diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 96f337dc081..03904aed599 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -3,6 +3,8 @@ module QA module Runtime module Env + prepend QA::EE::Runtime::Env + extend self attr_writer :personal_access_token, :ldap_username, :ldap_password diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index e05269e8d55..dcd7aab8b10 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -7,7 +7,7 @@ module QA class Selectors < Scenario::Template include Scenario::Bootable - PAGES = [QA::Page].freeze + PAGES = [QA::Page, QA::EE::Page].freeze def perform(*) validators = PAGES.map do |pages| diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb index b54fd5628f2..c5cddff56cd 100644 --- a/qa/qa/service/omnibus.rb +++ b/qa/qa/service/omnibus.rb @@ -11,11 +11,12 @@ module QA end def gitlab_ctl(command, input: nil) - if input.nil? - shell "docker exec #{@name} gitlab-ctl #{command}" - else - shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'" - end + docker_exec("gitlab-ctl #{command}", input: input) + end + + def docker_exec(command, input: nil) + command = "#{input} | #{command}" if input + shell "docker exec #{@name} bash -c '#{command}'" end end end diff --git a/qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb b/qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb new file mode 100644 index 00000000000..ae30478f60d --- /dev/null +++ b/qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +module QA + # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/48 + context 'Plan', :quarantine do + describe 'Epics milestone dates API' do + before(:context) do + @api_client = Runtime::API::Client.new(:gitlab) + @group_id = Resource::Group.fabricate_via_api!.id + @project_id = create_project + @milestone_start_date = (Date.today.to_date + 100).strftime("%Y-%m-%d") + @milestone_due_date = (Date.today.to_date + 120).strftime("%Y-%m-%d") + @fixed_start_date = Date.today.to_date.strftime("%Y-%m-%d") + @fixed_due_date = (Date.today.to_date + 90).strftime("%Y-%m-%d") + end + + def create_epic_issue_milestone + epic_iid = create_epic + milestone_id = create_milestone(@milestone_start_date, @milestone_due_date) + issue_id = create_issue(milestone_id) + add_issue_to_epic(epic_iid, issue_id) + use_epics_milestone_dates(epic_iid) + [epic_iid, milestone_id] + end + + def create_request(api_endpoint) + Runtime::API::Request.new(@api_client, api_endpoint) + end + + def create_project + project_name = "project_#{SecureRandom.hex(8)}" + create_project_request = create_request('/projects') + post create_project_request.url, path: project_name, name: project_name, namespace_id: @group_id + expect_status(201) + json_body[:id] + end + + def create_issue(milestone_id) + request = create_request("/projects/#{@project_id}/issues") + post request.url, title: 'My Test Issue', milestone_id: milestone_id + expect_status(201) + json_body[:id] + end + + def create_milestone(start_date, due_date) + request = create_request("/projects/#{@project_id}/milestones") + post request.url, title: "Test_Milestone_#{SecureRandom.hex(8)}", due_date: due_date, start_date: start_date + expect_status(201) + json_body[:id] + end + + def create_epic + request = create_request("/groups/#{@group_id}/epics") + post request.url, title: 'My New Epic', due_date_fixed: @fixed_due_date, start_date_fixed: @fixed_start_date, start_date_is_fixed: true, due_date_is_fixed: true + expect_status(201) + json_body[:iid] + end + + def add_issue_to_epic(epic_iid, issue_id) + # Add Issue with milestone to an epic + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues/#{issue_id}") + post request.url + + expect_status(201) + expect_json('epic.title', 'My New Epic*') + expect_json('issue.title', 'My Test Issue') + end + + def use_epics_milestone_dates(epic_iid) + # Update Epic to use Milestone Dates + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}") + put request.url, start_date_is_fixed: false, due_date_is_fixed: false + + expect_status(200) + expect_json('start_date_from_milestones', @milestone_start_date) + expect_json('due_date_from_milestones', @milestone_due_date) + expect_json('due_date_fixed', @fixed_due_date) + expect_json('start_date_fixed', @fixed_start_date) + expect_json('start_date', @milestone_start_date) + expect_json('due_date', @milestone_due_date) + end + + it 'Updating milestones changes epic dates' do + epic_iid, milestone_id = create_epic_issue_milestone + milestone_start_date = Date.today.to_date.strftime("%Y-%m-%d") + milestone_due_date = (Date.today.to_date + 30).strftime("%Y-%m-%d") + + # Update Milestone to different dates and see it reflecting in the epics + request = create_request("/projects/#{@project_id}/milestones/#{milestone_id}") + put request.url, start_date: milestone_start_date, due_date: milestone_due_date + expect_status(200) + + # Get Epic Details + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}") + get request.url + expect_status(200) + + expect_json('start_date_from_milestones', milestone_start_date) + expect_json('due_date_from_milestones', milestone_due_date) + expect_json('start_date', milestone_start_date) + expect_json('due_date', milestone_due_date) + end + + it 'Adding another issue updates epic dates' do + epic_iid = create_epic_issue_milestone[0] + milestone_start_date = Date.today.to_date.strftime("%Y-%m-%d") + milestone_due_date = (Date.today.to_date + 150).strftime("%Y-%m-%d") + + # Add another Issue and milestone + second_milestone_id = create_milestone(milestone_start_date, milestone_due_date) + second_issue_id = create_issue(second_milestone_id) + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues/#{second_issue_id}") + post request.url + expect_status(201) + + # and check milestone dates + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}") + get request.url + expect_status(200) + + expect_json('start_date_from_milestones', milestone_start_date) + expect_json('due_date_from_milestones', milestone_due_date) + expect_json('start_date', milestone_start_date) + expect_json('due_date', milestone_due_date) + end + + it 'Removing issue updates epic dates' do + epic_iid = create_epic_issue_milestone[0] + + # Get epic_issue_id + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues") + get request.url + expect_status(200) + epic_issue_id = json_body[0][:epic_issue_id] + + # Remove Issue + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues/#{epic_issue_id}") + delete request.url + expect_status(200) + + # and check milestone dates + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}") + get request.url + expect_status(200) + + expect_json('start_date_from_milestones', nil) + expect_json('due_date_from_milestones', nil) + expect_json('start_date', nil) + expect_json('due_date', nil) + end + + it 'Deleting milestones updates epic dates' do + epic_iid, milestone_id = create_epic_issue_milestone + + # Delete Milestone + request = create_request("/projects/#{@project_id}/milestones/#{milestone_id}") + delete request.url + expect_status(204) + + # and check milestone dates + request = create_request("/groups/#{@group_id}/epics/#{epic_iid}") + get request.url + expect_status(200) + + expect_json('start_date_from_milestones', nil) + expect_json('due_date_from_milestones', nil) + expect_json('start_date', nil) + expect_json('due_date', nil) + end + end + end +end diff --git a/qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb b/qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb new file mode 100644 index 00000000000..a13b06863c0 --- /dev/null +++ b/qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'Geo Nodes API' do + before(:all) do + get_personal_access_token + end + + shared_examples 'retrieving configuration about Geo nodes' do + it 'GET /geo_nodes' do + get api_endpoint('/geo_nodes') + + expect_status(200) + expect(json_body.size).to be >= 2 + expect_json('?', primary: true) + expect_json_types('*', primary: :boolean, current: :boolean, + files_max_capacity: :integer, repos_max_capacity: :integer, + clone_protocol: :string, _links: :object) + end + + it 'GET /geo_nodes/:id' do + get api_endpoint("/geo_nodes/#{geo_node[:id]}") + + expect_status(200) + expect(json_body).to eq geo_node + end + end + + describe 'Geo Nodes API on primary node', :geo do + before(:context) do + fetch_nodes(:geo_primary) + end + + include_examples 'retrieving configuration about Geo nodes' do + let(:geo_node) { @primary_node } + end + + describe 'editing a Geo node' do + it 'PUT /geo_nodes/:id for secondary node' do + endpoint = api_endpoint("/geo_nodes/#{@secondary_node[:id]}") + new_attributes = { enabled: false, files_max_capacity: 1000, repos_max_capacity: 2000 } + + put endpoint, new_attributes + + expect_status(200) + expect_json(new_attributes) + + # restore the original values + put endpoint, { enabled: @secondary_node[:enabled], + files_max_capacity: @secondary_node[:files_max_capacity], + repos_max_capacity: @secondary_node[:repos_max_capacity] } + + expect_status(200) + end + end + end + + describe 'Geo Nodes API on secondary node', :geo do + before(:context) do + fetch_nodes(:geo_secondary) + end + + include_examples 'retrieving configuration about Geo nodes' do + let(:geo_node) { @nodes.first } + end + end + + def api_endpoint(endpoint) + QA::Runtime::API::Request.new(@api_client, endpoint).url + end + + def fetch_nodes(node_type) + @api_client = Runtime::API::Client.new(node_type, personal_access_token: @personal_access_token) + + get api_endpoint('/geo_nodes') + + @nodes = json_body + @primary_node = @nodes.detect { |node| node[:primary] == true } + @secondary_node = @nodes.detect { |node| node[:primary] == false } + end + + # go to the primary and create a personal_access_token, which will be used + # for accessing both the primary and secondary + def get_personal_access_token + api_client = Runtime::API::Client.new(:geo_primary) + @personal_access_token = api_client.personal_access_token + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb new file mode 100644 index 00000000000..0b5b2540042 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +module QA + context 'Manage', :orchestrated, :ldap_tls, :ldap_no_tls do + describe 'LDAP Group sync' do + include Support::Api + + before(:all) do + # Create the sandbox group as the LDAP user. Without this the admin user + # would own the sandbox group and then in subsequent tests the LDAP user + # would not have enough permission to push etc. + Resource::Sandbox.fabricate_via_api! + + # Create an admin personal access token and use it for the remaining API calls + @original_personal_access_token = Runtime::Env.personal_access_token + + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area? + end + + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_admin_credentials) + + Runtime::Env.personal_access_token = Resource::PersonalAccessToken.fabricate!.access_token + Page::Main::Menu.perform(&:sign_out) + end + + after(:all) do + # Restore the original personal access token so that subsequent tests + # don't perform API calls as an admin user while logged in as a non-root + # LDAP user + Runtime::Env.personal_access_token = @original_personal_access_token + end + + context 'using group cn method' do + let(:ldap_users) do + [ + { + name: 'ENG User 1', + username: 'enguser1', + email: 'enguser1@example.org', + provider: 'ldapmain', + extern_uid: 'uid=enguser1,ou=people,ou=global groups,dc=example,dc=org' + }, + { + name: 'ENG User 2', + username: 'enguser2', + email: 'enguser2@example.org', + provider: 'ldapmain', + extern_uid: 'uid=enguser2,ou=people,ou=global groups,dc=example,dc=org' + }, + { + name: 'ENG User 3', + username: 'enguser3', + email: 'enguser3@example.org', + provider: 'ldapmain', + extern_uid: 'uid=enguser3,ou=people,ou=global groups,dc=example,dc=org' + } + ] + end + let(:owner_user) { 'enguser1' } + let(:sync_users) { ['ENG User 2', 'ENG User 3'] } + + before do + create_users_via_api(ldap_users) + group = create_group_and_add_user_via_api(owner_user, 'Synched-engineering-group') + signin_and_visit_group_as_user(owner_user, group) + + EE::Page::Group::Menu.perform(&:go_to_ldap_sync_settings) + + EE::Page::Group::Settings::LDAPSync.perform do |page| + page.set_sync_method('LDAP Group cn') + page.set_group_cn('Engineering') + page.click_add_sync_button + end + + EE::Page::Group::Menu.perform(&:click_group_members_item) + end + + it 'has LDAP users synced' do + verify_users_synced(sync_users) + end + end + + context 'user filter method' do + let(:ldap_users) do + [ + { + name: 'HR User 1', + username: 'hruser1', + email: 'hruser1@example.org', + provider: 'ldapmain', + extern_uid: 'uid=hruser1,ou=people,ou=global groups,dc=example,dc=org' + }, + { + name: 'HR User 2', + username: 'hruser2', + email: 'hruser2@example.org', + provider: 'ldapmain', + extern_uid: 'uid=hruser2,ou=people,ou=global groups,dc=example,dc=org' + }, + { + name: 'HR User 3', + username: 'hruser3', + email: 'hruser3@example.org', + provider: 'ldapmain', + extern_uid: 'uid=hruser3,ou=people,ou=global groups,dc=example,dc=org' + } + ] + end + let(:owner_user) { 'hruser1' } + let(:sync_users) { ['HR User 2', 'HR User 3'] } + + before do + create_users_via_api(ldap_users) + group = create_group_and_add_user_via_api(owner_user, 'Synched-human-resources-group') + signin_and_visit_group_as_user(owner_user, group) + + EE::Page::Group::Menu.perform(&:go_to_ldap_sync_settings) + + EE::Page::Group::Settings::LDAPSync.perform do |page| + page.set_user_filter('(&(objectClass=person)(cn=HR*))') + page.click_add_sync_button + end + + EE::Page::Group::Menu.perform(&:click_group_members_item) + end + + it 'has LDAP users synced' do + verify_users_synced(sync_users) + end + end + + def create_users_via_api(users) + users.each do |user| + Resource::User.fabricate_via_api! do |resource| + resource.username = user[:username] + resource.name = user[:name] + resource.email = user[:email] + resource.extern_uid = user[:extern_uid] + resource.provider = user[:provider] + end + end + end + + def add_user_to_group_via_api(user, group) + api_client = Runtime::API::Client.new(:gitlab) + response = get Runtime::API::Request.new(api_client, "/users?username=#{user}").url + post Runtime::API::Request.new(api_client, group.api_members_path).url, { user_id: parse_body(response).first[:id], access_level: '50' } + end + + def create_group_and_add_user_via_api(user_name, group_name) + group = Resource::Group.fabricate_via_api! do |resource| + resource.path = "#{group_name}-#{SecureRandom.hex(4)}" + end + + add_user_to_group_via_api(user_name, group) + + group + end + + def signin_and_visit_group_as_user(user_name, group) + user = Struct.new(:ldap_username, :ldap_password).new(user_name, 'password') + + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform do |login_page| + login_page.sign_in_using_ldap_credentials(user) + end + + group.visit! + end + + def verify_users_synced(expected_users) + EE::Page::Group::Members.perform do |page| + page.click_sync_now + users_synchronised = page.retry_until(reload: true) do + expected_users.map { |user| page.has_content?(user) }.all? + end + expect(users_synchronised).to be_truthy + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb new file mode 100644 index 00000000000..305cde0f6c9 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +module QA + context 'Manage', :orchestrated, :group_saml do + describe 'Group SAML SSO' do + include Support::Api + + before(:all) do + @group = Resource::Sandbox.fabricate! + end + + before do + unless Page::Main::Menu.perform(&:has_personal_area?) + + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + end + + @group.visit! + end + + it 'User logs in to group with SAML SSO' do + EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings) + + EE::Page::Group::Settings::SamlSSO.perform do |page| + page.set_id_provider_sso_url(QA::EE::Runtime::Saml.idp_sso_url) + page.set_cert_fingerprint(QA::EE::Runtime::Saml.idp_certificate_fingerprint) + page.click_save_changes + + page.click_user_login_url_link + end + + EE::Page::Group::SamlSSOSignIn.perform(&:click_signin) + + login_to_idp_if_required_and_expect_success + + EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings) + + EE::Page::Group::Settings::SamlSSO.perform(&:click_user_login_url_link) + + EE::Page::Group::SamlSSOSignIn.perform(&:click_signin) + + expect(page).to have_content("Already signed in with SAML for #{Runtime::Env.sandbox_name}") + end + + it 'Lets group admin test settings' do + EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings) + + EE::Page::Group::Settings::SamlSSO.perform do |page| + page.set_id_provider_sso_url(QA::EE::Runtime::Saml.idp_sso_url) + page.set_cert_fingerprint(QA::EE::Runtime::Saml.idp_certificate_fingerprint) + page.click_save_changes + + page.click_test_button + end + + login_to_idp_if_required_and_expect_success + + expect(page).to have_content("Test SAML SSO") + end + + context 'Enforced SSO' do + before do + Runtime::Feature.enable("enforced_sso") + Runtime::Feature.enable("enforced_sso_requires_session") + end + + it 'user clones and pushes to project within a group using Git HTTP' do + branch_name = "new_branch" + + user = Resource::User.new.tap do |user| + user.name = 'SAML Developer' + user.username = 'saml_dev' + end + + create_user_via_api(user) + + add_user_to_group_via_api(user.username, @group, '30') + + EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings) + + EE::Page::Group::Settings::SamlSSO.perform do |page| + page.enforce_sso + page.set_id_provider_sso_url(QA::EE::Runtime::Saml.idp_sso_url) + page.set_cert_fingerprint(QA::EE::Runtime::Saml.idp_certificate_fingerprint) + + page.click_save_changes + end + + @project = Resource::Project.fabricate! do |project| + project.name = 'project-in-saml-enforced-group' + project.description = 'project in SAML enforced gorup for git clone test' + project.group = @group + project.initialize_with_readme = true + end + + @project.visit! + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = @project + push.branch_name = branch_name + push.user = user + end + end + end + after(:all) do + remove_group(@group) + end + end + + def login_to_idp_if_required_and_expect_success + Vendor::SAMLIdp::Page::Login.perform { |login_page| login_page.login_if_required } + expect(page).to have_content("SAML for #{Runtime::Env.sandbox_name} was added to your connected accounts") + .or have_content("Already signed in with SAML for #{Runtime::Env.sandbox_name}") + end + + def remove_group(group) + api_client = Runtime::API::Client.new(:gitlab) + delete Runtime::API::Request.new(api_client, "/groups/#{group.path}").url + end + + def create_user_via_api(user) + Resource::User.fabricate_via_api! do |resource| + resource.username = user.username + resource.name = user.name + resource.email = user.email + resource.password = user.password + end + end + + def add_user_to_group_via_api(username, group, access_level) + api_client = Runtime::API::Client.new(:gitlab) + response = get Runtime::API::Request.new(api_client, "/users?username=#{username}").url + post Runtime::API::Request.new(api_client, group.api_members_path).url, { user_id: parse_body(response).first[:id], access_level: access_level } + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb new file mode 100644 index 00000000000..bf9fe383c0b --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module QA + context 'Manage', :orchestrated, :ldap_no_tls, :ldap_tls do + describe 'LDAP admin sync' do + it 'Syncs admin users' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + + Page::Main::Login.perform do |login_page| + user = Struct.new(:ldap_username, :ldap_password).new('adminuser1', 'password') + + login_page.sign_in_using_ldap_credentials(user) + end + + Page::Main::Menu.perform do |menu| + expect(menu).to have_personal_area + + # The ldap_sync_worker_cron job is set to run every minute + admin_synchronised = menu.wait(max: 80, interval: 1, reload: true) do + menu.has_admin_area_link? + end + + expect(admin_synchronised).to be_truthy + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb new file mode 100644 index 00000000000..5c6b77a6a10 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true +require 'securerandom' + +module QA + context :manage do + describe 'Project templates' do + before(:all) do + @files = [ + { + name: 'file.txt', + content: 'foo' + }, + { + name: 'README.md', + content: 'bar' + } + ] + + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + @template_container_group_name = "instance-template-container-group-#{SecureRandom.hex(8)}" + + template_container_group = QA::Resource::Group.fabricate! do |group| + group.path = @template_container_group_name + group.description = 'Instance template container group' + end + + @template_project = Resource::Project.fabricate! do |project| + project.name = 'template-project-1' + project.group = template_container_group + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = @template_project + push.files = @files + push.commit_message = 'Add test files' + end + end + + # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/51 + context 'instance level', :quarantine do + before do + # Log out if already logged in + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_admin_credentials) + + Page::Main::Menu.perform(&:click_admin_area) + Page::Admin::Menu.perform(&:go_to_template_settings) + + EE::Page::Admin::Settings::Templates.perform do |page| + page.choose_custom_project_template("#{@template_container_group_name}") + end + + group = Resource::Group.fabricate_via_api! + group.visit! + + Page::Group::Show.perform(&:go_to_new_project) + + Page::Project::New.perform(&:go_to_create_from_template_instance_tab) + end + + it 'successfully imports the project using template' do + Page::Project::New.perform do |page| + expect(page.instance_template_tab_badge_text).to eq "1" + expect(page).to have_text(@template_project.name) + end + + create_project_using_template(project_name: 'Project using instance level project template', + namespace: Runtime::Namespace.path, + template_name: @template_project.name) + + Page::Project::Show.perform(&:wait_for_import_success) + @files.each do |file| + expect(page).to have_content(file[:name]) + end + end + end + + context 'group level' do + before do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + Page::Main::Menu.perform(&:go_to_groups) + Page::Dashboard::Groups.perform { |page| page.click_group(Runtime::Namespace.sandbox_name) } + Page::Project::Menu.perform(&:click_settings) + + EE::Page::Group::Settings::General.perform do |settings| + settings.choose_custom_project_template("#{@template_container_group_name}") + end + + group = Resource::Group.fabricate_via_api! + group.visit! + + Page::Group::Show.perform(&:go_to_new_project) + + Page::Project::New.perform(&:go_to_create_from_template_group_tab) + end + + it 'successfully imports the project using template' do + Page::Project::New.perform do |page| + expect(page.group_template_tab_badge_text).to eq "1" + expect(page).to have_text(@template_container_group_name) + expect(page).to have_text(@template_project.name) + end + + create_project_using_template(project_name: 'Project using group level project template', + namespace: Runtime::Namespace.sandbox_name, + template_name: @template_project.name) + + Page::Project::Show.perform(&:wait_for_import_success) + @files.each do |file| + expect(page).to have_content(file[:name]) + end + end + end + + def create_project_using_template(project_name:, namespace:, template_name:) + Page::Project::New.perform do |page| + page.use_template_for_project(template_name) + page.choose_namespace(namespace) + page.choose_name("#{project_name} #{SecureRandom.hex(8)}") + page.add_description("#{project_name}") + page.set_visibility('Public') + page.create_new_project + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb new file mode 100644 index 00000000000..8c3e13a76b6 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module QA + # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/19 + context 'Plan', :quarantine do + describe 'Epics Creation' do + before(:all) do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + end + + it 'user creates, edits, deletes epic' do + issue = Resource::Issue.fabricate_via_api! do |issue| + issue.title = 'Issue for epics tests' + issue.labels = [] + end + + epic = EE::Resource::Epic.fabricate_via_browser_ui! do |epic| + epic.group = issue.project.group + epic.title = 'My First Epic' + end + + expect(page).to have_content('My First Epic') + + # Edit Epics + EE::Page::Group::Epic::Show.act { click_edit_button } + + EE::Page::Group::Epic::Edit.perform do |edit_page| + edit_page.set_description('My Edited Epic Description') + edit_page.set_title('My Edited Epic') + edit_page.save_changes + expect(edit_page).to have_content('My Edited Epic') + end + + # Add/Remove Issues to/from Epics + EE::Page::Group::Epic::Show.perform do |show_page| + show_page.add_issue_to_epic(issue.web_url) + expect(show_page).to have_content('added issue') + expect(show_page).to have_content('My Edited Epic') + + show_page.remove_issue_from_epic + expect(show_page).to have_content('removed issue') + end + + # Comment on Epics + EE::Page::Group::Epic::Show.act { add_comment_to_epic('My Epic Comments') } + + expect(page).to have_content('My Epic Comments') + + # Add Issue to Epic using quick actions + issue.visit! + + Page::Project::Issue::Show.perform do |show_page| + show_page.wait_for_related_issues_to_load + show_page.comment("/epic #{epic.web_url}") + show_page.comment("/remove_epic") + expect(show_page).to have_content('removed from epic') + end + + epic.visit! + + expect(page).to have_content('added issue', count: 2) + expect(page).to have_content('removed issue', count: 2) + + # Close Epic + EE::Page::Group::Epic::Show.act { close_reopen_epic } + + expect(page).to have_content('Closed') + + # Reopen Epic + EE::Page::Group::Epic::Show.act { close_reopen_epic } + + expect(page).to have_content('Open') + + # Delete Epics + EE::Page::Group::Epic::Show.act { click_edit_button } + + EE::Page::Group::Epic::Edit.perform do |edit_page| + edit_page.delete_epic + expect(edit_page).to have_content('The epic was successfully deleted') + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb new file mode 100644 index 00000000000..731da801700 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module QA + context 'Plan' do + describe 'promote issue to epic' do + let(:issue_title) { "My Awesome Issue #{SecureRandom.hex(8)}" } + + it 'user promotes issue to an epic' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + group = Resource::Group.fabricate_via_api! + + project = Resource::Project.fabricate_via_api! do |project| + project.name = 'promote-issue-to-epic' + project.description = 'Project to promote issue to epic' + project.group = group + end + + Resource::Issue.fabricate_via_browser_ui! do |issue| + issue.title = issue_title + issue.project = project + end + + Page::Project::Issue::Show.perform do |show| + # Due to the randomness of tests execution, sometimes a previous test + # may have changed the filter, which makes the below action needed. + # TODO: Make this test completely independent, not requiring the below step. + show.select_all_activities_filter + # We add a space together with the '/promote' string to avoid test flakiness + # due to the tooltip '/promote Promote issue to an epic (may expose + # confidential information)' from being shown, which may cause the click not + # to work properly. + show.comment('/promote ') + end + + group.visit! + QA::EE::Page::Group::Menu.perform(&:click_group_epics_link) + QA::EE::Page::Group::Epic::Index.perform do |index| + index.click_first_epic(QA::EE::Page::Group::Epic::Show) + end + + expect(page).to have_content(issue_title) + expect(page).to have_content(/promoted from issue .* \(closed\)/) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb new file mode 100644 index 00000000000..39ae71dd676 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb @@ -0,0 +1,40 @@ +module QA + context 'Plan' do + describe 'Editing scoped labels on issues' do + before do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + @initial_label = 'animal::fox' + @new_label_same_scope = 'animal::dolphin' + @new_label_different_scope = 'plant::orchid' + + issue = Resource::Issue.fabricate_via_api! do |issue| + issue.title = 'Issue to test the scoped labels' + issue.labels = @initial_label + end + + [@new_label_same_scope, @new_label_different_scope].each do |label| + Resource::Label.fabricate_via_api! do |l| + l.project = issue.project.id + l.title = label + end + end + + issue.visit! + end + + it 'correctly applies scoped labels depending on if they are from the same or a different scope' do + Page::Project::Issue::Show.perform do |issue_page| + issue_page.select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope] + + expect(page).to have_content("added #{@initial_label}") + expect(page).to have_content("added #{@new_label_same_scope} #{@new_label_different_scope} labels and removed #{@initial_label}") + expect(issue_page.text_of_labels_block).to have_content(@new_label_same_scope) + expect(issue_page.text_of_labels_block).to have_content(@new_label_different_scope) + expect(issue_page.text_of_labels_block).not_to have_content(@initial_label) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb new file mode 100644 index 00000000000..490f3d84d4a --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'batch comments in merge request' do + it 'user submits, discards batch comments' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + project = Resource::Project.fabricate! do |project| + project.name = 'project-with-merge-request' + end + + Resource::MergeRequest.fabricate! do |merge_request| + merge_request.title = 'This is a merge request' + merge_request.description = 'Great feature' + merge_request.project = project + end + + Page::MergeRequest::Show.perform do |show_page| + show_page.click_discussions_tab + + show_page.start_discussion("I'm starting a new discussion") + expect(show_page).to have_content("I'm starting a new discussion") + + show_page.type_reply_to_discussion("Could you please check this?") + show_page.comment_now + expect(show_page).to have_content("Could you please check this?") + expect(show_page).to have_content("0/1 discussion resolved") + + show_page.type_reply_to_discussion("Could you also check that?") + show_page.resolve_review_discussion + show_page.start_review + expect(show_page).to have_content("Could you also check that?") + expect(show_page).to have_content("Finish review 1") + + show_page.click_diffs_tab + + show_page.add_comment_to_diff("Can you check this line of code?") + show_page.comment_now + expect(show_page).to have_content("Can you check this line of code?") + + show_page.type_reply_to_discussion("And this syntax as well?") + show_page.resolve_review_discussion + show_page.start_review + expect(show_page).to have_content("And this syntax as well?") + expect(show_page).to have_content("Finish review 2") + + show_page.submit_pending_reviews + expect(show_page).to have_content("2/2 discussions resolved") + + show_page.type_reply_to_discussion("Unresolving this discussion") + show_page.unresolve_review_discussion + show_page.comment_now + expect(show_page).to have_content("1/2 discussions resolved") + end + + Page::MergeRequest::Show.perform do |show_page| + show_page.click_discussions_tab + + show_page.type_reply_to_discussion("Planning to discard this comment") + show_page.start_review + + expect(show_page).to have_content("Finish review 1") + show_page.discard_pending_reviews + + expect(show_page).not_to have_content("Planning to discard this comment") + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb new file mode 100644 index 00000000000..e10a8f7eec4 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module QA + # https://gitlab.com/gitlab-org/quality/staging/issues/39 + context 'Create', :quarantine do + describe 'Codeowners' do + it 'merge request assigns code owners as approvers' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create one user to be the assigned approver and another user who will + # not be an approver + approver = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) + non_approver = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) + + # Create a project and assign the users to it + project = Resource::Project.fabricate! do |project| + project.name = "assign-approvers" + end + project.visit! + + Page::Project::Menu.perform(&:go_to_members_settings) + Page::Project::Settings::Members.perform do |members_page| + members_page.add_member(approver.username) + members_page.add_member(non_approver.username) + end + + # Push CODEOWNERS to master + project_push = Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = 'CODEOWNERS' + push.file_content = <<~CONTENT + CODEOWNERS @#{approver.username} + CONTENT + push.commit_message = 'Add CODEOWNERS and test files' + end + + # Push a new CODEOWNERS file and create a merge request + Resource::MergeRequest.fabricate! do |merge_request| + merge_request.title = 'This is a merge request' + merge_request.description = 'Change code owners' + merge_request.project = project_push.project + merge_request.file_name = 'CODEOWNERS' + merge_request.file_content = <<~CONTENT + CODEOWNERS @#{non_approver.username} + CONTENT + end + + # Check that the merge request assigns the original code owner as an + # approver (because the current CODEOWNERS file in the master branch + # doesn't have the new owner yet) + Page::MergeRequest::Show.perform do |mr_page| + mr_page.edit! + expect(mr_page.approvers).to include(approver.name) + expect(mr_page.approvers).not_to include(non_approver.name) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb new file mode 100644 index 00000000000..a3d7f54e085 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Codeowners' do + let(:files) do + [ + { + name: 'file.txt', + content: 'foo' + }, + { + name: 'README.md', + content: 'bar' + } + ] + end + + before do + # Add two new users to a project as members + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + @user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) + @user2 = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) + + @project = Resource::Project.fabricate! do |project| + project.name = "codeowners" + end + @project.visit! + + Page::Project::Menu.perform(&:go_to_members_settings) + Page::Project::Settings::Members.perform do |members_page| + members_page.add_member(@user.username) + members_page.add_member(@user2.username) + end + end + + it 'displays owners specified in CODEOWNERS file' do + codeowners_file_content = + <<-CONTENT + * @#{@user2.username} + *.txt @#{@user.username} + CONTENT + files << { + name: 'CODEOWNERS', + content: codeowners_file_content + } + + # Push CODEOWNERS and test files to the project + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = @project + push.files = files + push.commit_message = 'Add CODEOWNERS and test files' + end + @project.visit! + + # Check the files and code owners + Page::Project::Show.perform do |project_page| + project_page.click_file 'file.txt' + end + + expect(page).to have_content(@user.name) + expect(page).not_to have_content(@user2.name) + + @project.visit! + Page::Project::Show.perform do |project_page| + project_page.click_file 'README.md' + end + + expect(page).to have_content(@user2.name) + expect(page).not_to have_content(@user.name) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb new file mode 100644 index 00000000000..6d1c8078ce7 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'GitLab Geo attachment replication' do + let(:file_to_attach) { File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) } + + after do + # Log out so subsequent tests can start unauthenticated + Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects) + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + end + + it 'user uploads attachment to the primary node' do + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + Page::Main::Login.perform(&:sign_in_using_credentials) + + @project = Resource::Project.fabricate! do |project| + project.name = 'project-for-issues' + project.description = 'project for adding issues' + end + + @issue = Resource::Issue.fabricate! do |issue| + issue.title = 'My geo issue' + issue.project = @project + end + + Page::Project::Issue::Show.perform do |show| + show.comment('See attached banana for scale', attachment: file_to_attach) + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do |session| + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + expect(page).to have_content 'You are on a secondary, read-only Geo node' + + Page::Main::Menu.perform do |menu| + menu.go_to_projects + end + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(@project.name) + + dashboard.go_to_project(@project.name) + end + + Page::Project::Menu.act { click_issues } + + Page::Project::Issue::Index.perform do |index| + index.wait_for_issue_replication(@issue) + end + + image_url = find('a[href$="banana_sample.gif"]')[:href] + + Page::Project::Issue::Show.perform do |show| + # Wait for attachment replication + found = show.wait(reload: false) do + show.asset_exists?(image_url) + end + + expect(found).to be_truthy + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb new file mode 100644 index 00000000000..36a932f7f2d --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'GitLab HTTP push' do + let(:file_name) { 'README.md' } + + after do + # Log out so subsequent tests can start unauthenticated + Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects) + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + end + + context 'regular git commit' do + it 'is replicated to the secondary' do + file_content = 'This is a Geo project! Commit from primary.' + project = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over HTTP directly to the primary + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = file_name + push.file_content = "# #{file_content}" + push.commit_message = 'Add README.md' + end + + # Validate git push worked and file exists with content + Page::Project::Show.perform do |show| + show.wait_for_repository_replication + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + Page::Main::Menu.perform { |menu| menu.go_to_projects } + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Validate the content has been sync'd from the primary + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_name) + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + end + end + end + end + + context 'git-lfs commit' do + it 'is replicated to the secondary' do + file_content = 'This is a Geo project!' + lfs_file_content = 'The rendered file could not be displayed because it is stored in LFS.' + project = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over HTTP directly to the primary + push = Resource::Repository::ProjectPush.fabricate! do |push| + push.use_lfs = true + push.project = project + push.file_name = file_name + push.file_content = "# #{file_content}" + push.commit_message = 'Add README.md' + end + + expect(push.output).to match(/Locking support detected on remote/) + + # Validate git push worked and file exists with content + Page::Project::Show.perform do |show| + show.wait_for_repository_replication + + expect(page).to have_content(file_name) + expect(page).to have_content(lfs_file_content) + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + Page::Main::Menu.perform { |menu| menu.go_to_projects } + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Validate the content has been sync'd from the primary + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_name) + + expect(page).to have_content(file_name) + expect(page).to have_content(lfs_file_content) + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb new file mode 100644 index 00000000000..f7f2c37a105 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'GitLab Geo HTTP push secondary' do + let(:file_content_primary) { 'This is a Geo project! Commit from primary.' } + let(:file_content_secondary) { 'This is a Geo project! Commit from secondary.' } + + after do + # Log out so subsequent tests can start unauthenticated + Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects) + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + end + + context 'regular git commit' do + it 'is redirected to the primary and ultimately replicated to the secondary' do + file_name = 'README.md' + project = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over HTTP directly to the primary + # + # This push is required to ensure we have the primary credentials + # written out to the .netrc + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = file_name + push.file_content = "# #{file_content_primary}" + push.commit_message = "Add #{file_name}" + end + project.visit! + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + Page::Main::Menu.perform(&:go_to_projects) + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Grab the HTTP URI for the secondary and store as 'location' + location = Page::Project::Show.perform do |project_page| + project_page.wait_for_repository_replication + project_page.repository_clone_http_location + end + + # Perform a git push over HTTP at the secondary + push = Resource::Repository::Push.fabricate! do |push| + push.new_branch = false + push.repository_http_uri = location.uri + push.file_name = file_name + push.file_content = "# #{file_content_secondary}" + push.commit_message = "Update #{file_name}" + end + + # We need to strip off the user from the URI, otherwise we won't + # get the correct output produced from the git CLI. + primary_uri = project.repository_http_location.uri + primary_uri.user = nil + + # The git cli produces the 'warning: redirecting to..' output + # internally. + expect(push.output).to match(/warning: redirecting to #{primary_uri.to_s}/) + + # Validate git push worked and new content is visible + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_content_secondary) + show.refresh + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content_secondary) + end + end + end + end + + context 'git-lfs commit' do + it 'is redirected to the primary and ultimately replicated to the secondary' do + file_name_primary = 'README.md' + file_name_secondary = 'README_MORE.md' + project = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over HTTP directly to the primary + # + # This push is required to ensure we have the primary credentials + # written out to the .netrc + Resource::Repository::Push.fabricate! do |push| + push.use_lfs = true + push.repository_http_uri = project.repository_http_location.uri + push.file_name = file_name_primary + push.file_content = "# #{file_content_primary}" + push.commit_message = "Add #{file_name_primary}" + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + Page::Main::Menu.perform(&:go_to_projects) + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Grab the HTTP URI for the secondary and store as 'location' + location = Page::Project::Show.perform do |project_page| + project_page.wait_for_repository_replication + project_page.repository_clone_http_location + end + + # Perform a git push over HTTP at the secondary + push = Resource::Repository::Push.fabricate! do |push| + push.use_lfs = true + push.new_branch = false + push.repository_http_uri = location.uri + push.file_name = file_name_secondary + push.file_content = "# #{file_content_secondary}" + push.commit_message = "Add #{file_name_secondary}" + end + + # We need to strip off the user from the URI, otherwise we won't + # get the correct output produced from the git CLI. + primary_uri = project.repository_http_location.uri + primary_uri.user = nil + + # The git cli produces the 'warning: redirecting to..' output + # internally. + expect(push.output).to match(/warning: redirecting to #{primary_uri.to_s}/) + expect(push.output).to match(/Locking support detected on remote "#{location.uri.to_s}"/) + + # Validate git push worked and new content is visible + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_name_secondary) + show.refresh + + expect(page).to have_content(file_name_secondary) + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb new file mode 100644 index 00000000000..7eb7ceb371b --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'GitLab Geo project rename replication' do + after do + # Log out so subsequent tests can start unauthenticated + Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects) + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + end + + it 'user renames project' do + # create the project and push code + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + Page::Main::Login.perform(&:sign_in_using_credentials) + + project = Resource::Project.fabricate! do |project| + project.name = 'geo-before-rename' + project.description = 'Geo project to be renamed' + end + + geo_project_name = project.name + expect(project.name).to include 'geo-before-rename' + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = 'README.md' + push.file_content = '# This is Geo project!' + push.commit_message = 'Add README.md' + end + + # rename the project + Page::Main::Menu.act { go_to_projects } + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.go_to_project(geo_project_name) + end + + Page::Project::Menu.act { click_settings } + + @geo_project_renamed = "geo-after-rename-#{SecureRandom.hex(8)}" + Page::Project::Settings::Main.perform do |settings| + settings.rename_project_to(@geo_project_renamed) + expect(page).to have_content "Project '#{@geo_project_renamed}' was successfully updated." + + settings.expand_advanced_settings do |page| + page.update_project_path_to(@geo_project_renamed) + end + end + end + + # check renamed project exist on secondary node + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + Page::Main::Menu.perform do |menu| + menu.go_to_projects + end + + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(@geo_project_renamed) + + dashboard.go_to_project(@geo_project_renamed) + end + + Page::Project::Show.perform do |show| + show.wait_for_repository_replication + + expect(page).to have_content 'README.md' + expect(page).to have_content 'This is Geo project!' + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb new file mode 100644 index 00000000000..ab664bf659f --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'GitLab SSH push' do + let(:file_name) { 'README.md' } + + after do + # Log out so subsequent tests can start unauthenticated + Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects) + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + end + + context 'regular git commit' do + it "is replicated to the secondary" do + key_title = "key for ssh tests #{Time.now.to_f}" + file_content = 'This is a Geo project! Commit from primary.' + project = nil + key = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new SSH key for the user + key = Resource::SSHKey.fabricate! do |resource| + resource.title = key_title + end + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over SSH directly to the primary + Resource::Repository::ProjectPush.fabricate! do |push| + push.ssh_key = key + push.project = project + push.file_name = file_name + push.file_content = "# #{file_content}" + push.commit_message = 'Add README.md' + end + + # Validate git push worked and file exists with content + Page::Project::Show.perform do |show| + show.wait_for_repository_replication + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + # Ensure the SSH key has replicated + Page::Main::Menu.act { click_settings_link } + Page::Profile::Menu.act { click_ssh_keys } + + expect(page).to have_content(key_title) + expect(page).to have_content(key.fingerprint) + + # Ensure project has replicated + Page::Main::Menu.perform { |menu| menu.go_to_projects } + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Validate the content has been sync'd from the primary + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_content) + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + end + end + end + end + + context 'git-lfs commit' do + it "is replicated to the secondary" do + key_title = "key for ssh tests #{Time.now.to_f}" + file_content = 'The rendered file could not be displayed because it is stored in LFS.' + project = nil + key = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new SSH key for the user + key = Resource::SSHKey.fabricate! do |resource| + resource.title = key_title + end + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over SSH directly to the primary + push = Resource::Repository::ProjectPush.fabricate! do |push| + push.use_lfs = true + push.ssh_key = key + push.project = project + push.file_name = file_name + push.file_content = "# #{file_content}" + push.commit_message = 'Add README.md' + end + + expect(push.output).to match(/Locking support detected on remote/) + + # Validate git push worked and file exists with content + Page::Project::Show.perform do |show| + show.wait_for_repository_replication + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + # Ensure the SSH key has replicated + Page::Main::Menu.act { click_settings_link } + Page::Profile::Menu.act { click_ssh_keys } + + expect(page).to have_content(key_title) + expect(page).to have_content(key.fingerprint) + + # Ensure project has replicated + Page::Main::Menu.perform { |menu| menu.go_to_projects } + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Validate the content has been sync'd from the primary + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_name) + + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb new file mode 100644 index 00000000000..188615420fd --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +module QA + context 'Geo', :orchestrated, :geo do + describe 'GitLab SSH push to secondary' do + let(:file_content_primary) { 'This is a Geo project! Commit from primary.' } + let(:file_content_secondary) { 'This is a Geo project! Commit from secondary.' } + + after do + # Log out so subsequent tests can start unauthenticated + Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects) + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + end + + context 'regular git commit' do + it 'is proxied to the primary and ultimately replicated to the secondary' do + file_name = 'README.md' + key_title = "key for ssh tests #{Time.now.to_f}" + file_content = 'This is a Geo project! Commit from secondary.' + project = nil + key = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new SSH key for the user + key = Resource::SSHKey.fabricate! do |resource| + resource.title = key_title + end + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over SSH directly to the primary + # + # This push is required to ensure we have the primary credentials + # written out to the .netrc + Resource::Repository::ProjectPush.fabricate! do |push| + push.ssh_key = key + push.project = project + push.file_name = file_name + push.file_content = "# #{file_content_primary}" + push.commit_message = "Add #{file_name}" + end + project.visit! + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + # Ensure the SSH key has replicated + Page::Main::Menu.perform(&:click_settings_link) + Page::Profile::Menu.perform do |menu| + menu.click_ssh_keys + menu.wait_for_key_to_replicate(key_title) + end + + expect(page).to have_content(key_title) + expect(page).to have_content(key.fingerprint) + + # Ensure project has replicated + Page::Main::Menu.perform(&:go_to_projects) + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Grab the SSH URI for the secondary and store as 'location' + location = Page::Project::Show.perform do |project_page| + project_page.wait_for_repository_replication + project_page.repository_clone_ssh_location + end + + # Perform a git push over SSH at the secondary + push = Resource::Repository::Push.fabricate! do |push| + push.new_branch = false + push.ssh_key = key + push.repository_ssh_uri = location.uri + push.file_name = file_name + push.file_content = "# #{file_content_secondary}" + push.commit_message = "Update #{file_name}" + end + + # Remove ssh:// from the URI to ensure we can match accurately + # as ssh:// can appear depending on how GitLab is configured. + ssh_uri = project.repository_ssh_location.git_uri.to_s.gsub(%r{ssh://}, '') + + expect(push.output).to match(%r{GitLab: We'll help you by proxying this request to the primary: (?:ssh://)?#{ssh_uri}}) + + # Validate git push worked and new content is visible + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_content) + + expect(page).to have_content(file_content) + end + end + end + end + + context 'git-lfs commit' do + it 'is proxied to the primary and ultimately replicated to the secondary' do + key_title = "key for ssh tests #{Time.now.to_f}" + file_name_primary = 'README.md' + file_name_secondary = 'README_MORE.md' + project = nil + key = nil + + Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do + # Visit the primary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a new SSH key for the user + key = Resource::SSHKey.fabricate! do |resource| + resource.title = key_title + end + + # Create a new Project + project = Resource::Project.fabricate! do |project| + project.name = 'geo-project' + project.description = 'Geo test project' + end + + # Perform a git push over SSH directly to the primary + # + # This push is required to ensure we have the primary credentials + # written out to the .netrc + Resource::Repository::Push.fabricate! do |push| + push.use_lfs = true + push.ssh_key = key + push.repository_ssh_uri = project.repository_ssh_location.uri + push.file_name = file_name_primary + push.file_content = "# #{file_content_primary}" + push.commit_message = "Add #{file_name_primary}" + end + end + + Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do + # Visit the secondary node and login + Page::Main::Login.perform(&:sign_in_using_credentials) + + EE::Page::Main::Banner.perform do |banner| + expect(banner).to have_secondary_read_only_banner + end + + # Ensure the SSH key has replicated + Page::Main::Menu.perform(&:click_settings_link) + Page::Profile::Menu.perform do |menu| + menu.click_ssh_keys + menu.wait_for_key_to_replicate(key_title) + end + + expect(page).to have_content(key_title) + expect(page).to have_content(key.fingerprint) + + # Ensure project has replicated + Page::Main::Menu.perform(&:go_to_projects) + Page::Dashboard::Projects.perform do |dashboard| + dashboard.wait_for_project_replication(project.name) + dashboard.go_to_project(project.name) + end + + # Grab the SSH URI for the secondary and store as 'location' + location = Page::Project::Show.perform do |project_page| + project_page.wait_for_repository_replication + project_page.repository_clone_ssh_location + end + + # Perform a git push over SSH at the secondary + push = Resource::Repository::Push.fabricate! do |push| + push.use_lfs = true + push.new_branch = false + push.ssh_key = key + push.repository_ssh_uri = location.uri + push.file_name = file_name_secondary + push.file_content = "# #{file_content_secondary}" + push.commit_message = "Add #{file_name_secondary}" + end + + ssh_uri = project.repository_ssh_location.git_uri.to_s.gsub(%r{ssh://}, '') + expect(push.output).to match(%r{GitLab: We'll help you by proxying this request to the primary: (?:ssh://)?#{ssh_uri}}) + expect(push.output).to match(/Locking support detected on remote "#{location.uri.to_s}"/) + + # Validate git push worked and new content is visible + Page::Project::Show.perform do |show| + show.wait_for_repository_replication_with(file_name_secondary) + show.refresh + + expect(page).to have_content(file_name_secondary) + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb b/qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb new file mode 100644 index 00000000000..f52651f9376 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'pathname' + +module QA + context 'Secure', :docker do + def login + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + end + + describe 'Security Dashboard support' do + let(:executor) { "qa-runner-#{Time.now.to_i}" } + + after do + Service::Runner.new(executor).remove! + end + + it 'displays the Dependency Scanning report in the pipeline' do + login + + @project = Resource::Project.fabricate! do |p| + p.name = Runtime::Env.auto_devops_project_name || 'project-with-secure' + p.description = 'Project with Secure' + end + + Resource::Runner.fabricate! do |runner| + runner.project = @project + runner.name = executor + runner.tags = %w[qa test] + end + + # Create Secure compatible repo + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = @project + push.directory = Pathname + .new(__dir__) + .join('../../../../ee/fixtures/secure_premade_reports') + push.commit_message = 'Create Secure compatible application to serve premade reports' + end + + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('dependency-scanning') + end + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 600) + + job.click_element(:pipeline_path) + end + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_on_security + expect(pipeline).to have_dependency_report + expect(pipeline).to have_content("Dependency scanning detected 1") + pipeline.expand_dependency_report + expect(pipeline).to have_content("jQuery before 3.4.0") + end + end + end + end +end diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb index 9c1f9904a7a..1b8c926532a 100644 --- a/qa/qa/vendor/saml_idp/page/login.rb +++ b/qa/qa/vendor/saml_idp/page/login.rb @@ -12,6 +12,14 @@ module QA fill_in 'password', with: 'user1pass' click_on 'Login' end + + def login_if_required + login if login_required? + end + + def login_required? + page.has_text?('Enter your username and password') + end end end end diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb index bf3ebce0cfe..2f9e4958ae1 100644 --- a/qa/spec/resource/repository/push_spec.rb +++ b/qa/spec/resource/repository/push_spec.rb @@ -19,7 +19,11 @@ describe QA::Resource::Repository::Push do expect { subject.files = [] }.to raise_error(ArgumentError) end - it 'does not raise if files is an array' do + it 'raises an error if files is not an array of hashes with :name and :content keys' do + expect { subject.files = [{ foo: 'foo' }] }.to raise_error(ArgumentError) + end + + it 'does not raise if files is an array of hashes with :name and :content keys' do expect { subject.files = files }.not_to raise_error end end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index caf96a213e1..340831aa06d 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -192,6 +192,30 @@ describe QA::Runtime::Env do end end + describe '.knapsack?' do + it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do + stub_env('KNAPSACK_GENERATE_REPORT', 'true') + + expect(described_class.knapsack?).to be_truthy + end + + it 'returns true if KNAPSACK_REPORT_PATH is defined' do + stub_env('KNAPSACK_REPORT_PATH', '/a/path') + + expect(described_class.knapsack?).to be_truthy + end + + it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do + stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern') + + expect(described_class.knapsack?).to be_truthy + end + + it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do + expect(described_class.knapsack?).to be_falsey + end + end + describe '.require_github_access_token!' do it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do stub_env('GITHUB_ACCESS_TOKEN', nil) |