Commit b66e5957 authored by Mark Lapierre's avatar Mark Lapierre

Merge branch 'qa-ml-add-e2e-test-pull-mirroring-with-ssh-key' into 'master'

Add e2e test of SSH pull mirroring with a private key

Closes gitlab-org/quality/testcases#228

See merge request gitlab-org/gitlab!16768
parents 3873a8a1 bcbeeaa2
- expanded = expanded_by_default? - expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) } %section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded), data: { qa_selector: 'mirroring_repositories_settings_section' } }
.settings-header .settings-header
%h4= _('Mirroring repositories') %h4= _('Mirroring repositories')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
...@@ -59,10 +59,10 @@ ...@@ -59,10 +59,10 @@
- if mirror.disabled? - if mirror.disabled?
= render 'projects/mirrors/disabled_mirror_badge' = render 'projects/mirrors/disabled_mirror_badge'
- if mirror.last_error.present? - if mirror.last_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
%td %td
.btn-group.mirror-actions-group.pull-right{ role: 'group' } .btn-group.mirror-actions-group.pull-right{ role: 'group' }
- if mirror.ssh_key_auth? - if mirror.ssh_key_auth?
= clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key')) = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
= render 'shared/remote_mirror_update_button', remote_mirror: mirror = render 'shared/remote_mirror_update_button', remote_mirror: mirror
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
- verified_at = mirror.ssh_known_hosts_verified_at - verified_at = mirror.ssh_known_hosts_verified_at
.form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) } .form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) }
%button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button' } %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button', data: { qa_selector: 'detect_host_keys' } }
= icon('spinner spin', class: 'js-spinner d-none') = icon('spinner spin', class: 'js-spinner d-none')
= _('Detect host keys') = _('Detect host keys')
.fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) } .fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) }
%label.label-bold %label.label-bold
= _('Fingerprints') = _('Fingerprints')
.fingerprints-list.js-fingerprints-list .fingerprints-list.js-fingerprints-list{ data: { qa_selector: 'fingerprints_list' } }
- mirror.ssh_known_hosts_fingerprints.each do |fp| - mirror.ssh_known_hosts_fingerprints.each do |fp|
%code= fp.fingerprint %code= fp.fingerprint
- if verified_at - if verified_at
......
- if remote_mirror.update_in_progress? - if remote_mirror.update_in_progress?
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') } %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body', qa_selector: 'updating_button' }, title: _('Updating') }
= icon("refresh spin") = icon("refresh spin")
- elsif remote_mirror.enabled? - elsif remote_mirror.enabled?
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
......
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
.btn-group.mirror-actions-group.pull-right{ role: 'group' } .btn-group.mirror-actions-group.pull-right{ role: 'group' }
- ssh_public_key = @project.import_data.ssh_public_key - ssh_public_key = @project.import_data.ssh_public_key
- if ssh_public_key - if ssh_public_key
= clipboard_button(text: ssh_public_key, class: 'btn btn-default qa-copy-ssh-public-key', title: _('Copy SSH public key')) = clipboard_button(text: ssh_public_key, class: 'btn btn-default rspec-copy-ssh-public-key', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
- if import_state.mirror_update_due? || import_state.updating_mirror? - if import_state.mirror_update_due? || import_state.updating_mirror?
%button.btn.disabled{ type: 'button', data: { container: 'body', toggle: 'tooltip' }, title: _('Updating') }= icon("refresh spin") %button.btn.disabled{ type: 'button', data: { container: 'body', toggle: 'tooltip', qa_selector: 'updating_button' }, title: _('Updating') }= icon("refresh spin")
- else - else
= link_to update_now_project_mirror_path(@project), method: :post, class: 'btn js-force-update-mirror', data: { container: 'body', toggle: 'tooltip', qa_selector: 'update_now_button' }, title: _('Update now') do = link_to update_now_project_mirror_path(@project), method: :post, class: 'btn js-force-update-mirror', data: { container: 'body', toggle: 'tooltip', qa_selector: 'update_now_button' }, title: _('Update now') do
= icon("refresh") = icon("refresh")
......
...@@ -148,7 +148,7 @@ describe 'Project mirror', :js do ...@@ -148,7 +148,7 @@ describe 'Project mirror', :js do
expect(page).to have_content('Mirroring settings were successfully updated') expect(page).to have_content('Mirroring settings were successfully updated')
expect(page).not_to have_content('Verified by') expect(page).not_to have_content('Verified by')
expect(find('.qa-copy-ssh-public-key')['data-clipboard-text']).to eq(import_data.ssh_public_key) expect(find('.rspec-copy-ssh-public-key')['data-clipboard-text']).to eq(import_data.ssh_public_key)
expect(project.mirror?).to be_truthy expect(project.mirror?).to be_truthy
expect(project.username_only_import_url).to eq('ssh://user@example.com') expect(project.username_only_import_url).to eq('ssh://user@example.com')
expect(import_data.auth_method).to eq('ssh_public_key') expect(import_data.auth_method).to eq('ssh_public_key')
......
...@@ -17,6 +17,8 @@ module QA ...@@ -17,6 +17,8 @@ module QA
element :mirror_repository_url_cell element :mirror_repository_url_cell
element :mirrored_repository_row element :mirrored_repository_row
element :update_now_button element :update_now_button
element :updating_button
element :copy_public_key_button
end end
end end
end end
......
...@@ -15,6 +15,10 @@ module QA ...@@ -15,6 +15,10 @@ module QA
def_delegators :evaluator, :view, :views def_delegators :evaluator, :view, :views
def assert_no_element(name)
assert_no_selector(element_selector_css(name))
end
def refresh def refresh
page.refresh page.refresh
end end
...@@ -102,9 +106,9 @@ module QA ...@@ -102,9 +106,9 @@ module QA
def select_element(name, value) def select_element(name, value)
element = find_element(name) element = find_element(name)
return if element.text.downcase.to_s == value.to_s return if element.text == value
element.select value.to_s.capitalize element.select value
end end
def has_element?(name, text: nil, wait: Capybara.default_max_wait_time) def has_element?(name, text: nil, wait: Capybara.default_max_wait_time)
......
...@@ -15,7 +15,9 @@ module QA ...@@ -15,7 +15,9 @@ module QA
element :mirror_repository_button element :mirror_repository_button
element :mirror_repository_url_cell element :mirror_repository_url_cell
element :mirror_last_update_at_cell element :mirror_last_update_at_cell
element :mirror_error_badge
element :mirrored_repository_row element :mirrored_repository_row
element :copy_public_key_button
end end
view 'app/views/projects/mirrors/_mirror_repos_form.html.haml' do view 'app/views/projects/mirrors/_mirror_repos_form.html.haml' do
...@@ -24,6 +26,17 @@ module QA ...@@ -24,6 +26,17 @@ module QA
view 'app/views/shared/_remote_mirror_update_button.html.haml' do view 'app/views/shared/_remote_mirror_update_button.html.haml' do
element :update_now_button element :update_now_button
element :updating_button
end
view 'app/views/projects/mirrors/_ssh_host_keys.html.haml' do
element :detect_host_keys
element :fingerprints_list
end
view 'app/views/projects/mirrors/_authentication_method.html.haml' do
element :authentication_method
element :password
end end
def repository_url=(value) def repository_url=(value)
...@@ -35,17 +48,40 @@ module QA ...@@ -35,17 +48,40 @@ module QA
end end
def mirror_direction=(value) def mirror_direction=(value)
raise ArgumentError, "Mirror direction must be :push or :pull" unless [:push, :pull].include? value raise ArgumentError, "Mirror direction must be 'Push' or 'Pull'" unless %w(Push Pull).include? value
select_element(:mirror_direction, value) select_element(:mirror_direction, value)
# Changing the mirror direction causes the fields below to change,
# and that change is animated, so we need to wait for the animation
# to complete otherwise changes to those fields could fail
wait_for_animated_element :authentication_method
end end
def authentication_method=(value) def authentication_method=(value)
raise ArgumentError, "Authentication method must be :password or :none" unless [:password, :none].include? value raise ArgumentError, "Authentication method must be 'SSH public key', 'Password', or 'None'" unless %w(Password None SSH\ public\ key).include? value
select_element(:authentication_method, value) select_element(:authentication_method, value)
end end
def public_key(url)
row_index = find_repository_row_index url
within_element_by_index(:mirrored_repository_row, row_index) do
find_element(:copy_public_key_button)['data-clipboard-text']
end
end
def detect_host_keys
click_element :detect_host_keys
# The host key detection process is interrupted if we navigate away
# from the page before the fingerprint appears.
wait(max: 5) do
find_element(:fingerprints_list).has_text? /.*/
end
end
def mirror_repository def mirror_repository
click_element :mirror_repository_button click_element :mirror_repository_button
end end
...@@ -54,7 +90,9 @@ module QA ...@@ -54,7 +90,9 @@ module QA
row_index = find_repository_row_index url row_index = find_repository_row_index url
within_element_by_index(:mirrored_repository_row, row_index) do within_element_by_index(:mirrored_repository_row, row_index) do
click_element :update_now_button # When a repository is first mirrored, the update process might
# already be started, so the button is already "clicked"
click_element :update_now_button unless has_element? :updating_button
end end
# Wait a few seconds for the sync to occur and then refresh the page # Wait a few seconds for the sync to occur and then refresh the page
...@@ -72,16 +110,19 @@ module QA ...@@ -72,16 +110,19 @@ module QA
# Fail early if the page still shows that there has been no update # Fail early if the page still shows that there has been no update
within_element_by_index(:mirrored_repository_row, row_index) do within_element_by_index(:mirrored_repository_row, row_index) do
find_element(:mirror_last_update_at_cell, wait: 0).assert_no_text('Never') find_element(:mirror_last_update_at_cell, wait: 0).assert_no_text('Never')
assert_no_element(:mirror_error_badge)
end end
end end
private private
def find_repository_row_index(target_url) def find_repository_row_index(target_url)
all_elements(:mirror_repository_url_cell).index do |url| wait(max: 5, reload: false) do
# The url might be a sanitized url but the target_url won't be so all_elements(:mirror_repository_url_cell).index do |url|
# we compare just the paths instead of the full url # The url might be a sanitized url but the target_url won't be so
URI.parse(url.text).path == target_url.path # we compare just the paths instead of the full url
URI.parse(url.text).path == target_url.path
end
end end
end end
end end
......
...@@ -16,7 +16,7 @@ module QA ...@@ -16,7 +16,7 @@ module QA
end end
view 'app/views/projects/mirrors/_mirror_repos.html.haml' do view 'app/views/projects/mirrors/_mirror_repos.html.haml' do
element :mirroring_repositories_settings element :mirroring_repositories_settings_section
end end
def expand_deploy_keys(&block) def expand_deploy_keys(&block)
...@@ -38,7 +38,7 @@ module QA ...@@ -38,7 +38,7 @@ module QA
end end
def expand_mirroring_repositories(&block) def expand_mirroring_repositories(&block)
expand_section(:mirroring_repositories_settings) do expand_section(:mirroring_repositories_settings_section) do
MirroringRepositories.perform(&block) MirroringRepositories.perform(&block)
end end
end end
......
...@@ -25,8 +25,8 @@ module QA ...@@ -25,8 +25,8 @@ module QA
settings.expand_mirroring_repositories do |mirror_settings| settings.expand_mirroring_repositories do |mirror_settings|
# Configure the source project to push to the target project # Configure the source project to push to the target project
mirror_settings.repository_url = target_project_uri mirror_settings.repository_url = target_project_uri
mirror_settings.mirror_direction = :push mirror_settings.mirror_direction = 'Push'
mirror_settings.authentication_method = :password mirror_settings.authentication_method = 'Password'
mirror_settings.password = Runtime::User.password mirror_settings.password = Runtime::User.password
mirror_settings.mirror_repository mirror_settings.mirror_repository
mirror_settings.update target_project_uri mirror_settings.update target_project_uri
......
...@@ -70,7 +70,7 @@ module QA ...@@ -70,7 +70,7 @@ module QA
Page::Project::Settings::Main.perform do |settings| Page::Project::Settings::Main.perform do |settings|
# Change visibility from public to internal # Change visibility from public to internal
settings.expand_visibility_project_features_permissions do |page| settings.expand_visibility_project_features_permissions do |page|
page.set_project_visibility "internal" page.set_project_visibility "Internal"
end end
end end
end end
......
...@@ -26,8 +26,8 @@ module QA ...@@ -26,8 +26,8 @@ module QA
settings.expand_mirroring_repositories do |mirror_settings| settings.expand_mirroring_repositories do |mirror_settings|
# Configure the target project to pull from the source project # Configure the target project to pull from the source project
mirror_settings.repository_url = source_project_uri mirror_settings.repository_url = source_project_uri
mirror_settings.mirror_direction = :pull mirror_settings.mirror_direction = 'Pull'
mirror_settings.authentication_method = :password mirror_settings.authentication_method = 'Password'
mirror_settings.password = Runtime::User.password mirror_settings.password = Runtime::User.password
mirror_settings.mirror_repository mirror_settings.mirror_repository
mirror_settings.update source_project_uri mirror_settings.update source_project_uri
......
# frozen_string_literal: true
module QA
context 'Create' do
describe 'Pull mirror a repository over SSH with a private key' do
let(:source) do
Resource::Repository::ProjectPush.fabricate! do |project_push|
project_push.project_name = 'pull-mirror-source-project'
project_push.file_name = 'README.md'
project_push.file_content = '# This is a pull mirroring test project'
project_push.commit_message = 'Add README.md'
end
end
let(:source_project_uri) { source.project.repository_ssh_location.uri }
let(:target_project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pull-mirror-target-project'
end
end
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
target_project.visit!
end
it 'configures and syncs a (pull) mirrored repository' do
# Configure the target project to pull from the source project
# And get the public key to be used as a deploy key
Page::Project::Menu.perform(&:go_to_repository_settings)
public_key = Page::Project::Settings::Repository.perform do |settings|
settings.expand_mirroring_repositories do |mirror_settings|
mirror_settings.repository_url = source_project_uri
mirror_settings.mirror_direction = 'Pull'
mirror_settings.authentication_method = 'SSH public key'
mirror_settings.detect_host_keys
mirror_settings.mirror_repository
mirror_settings.public_key source_project_uri
end
end
# Add the public key to the source project as a deploy key
Resource::DeployKey.fabricate! do |deploy_key|
deploy_key.project = source.project
deploy_key.title = "pull mirror key #{Time.now.to_f}"
deploy_key.key = public_key
end
# Sync the repositories
target_project.visit!
Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |settings|
settings.expand_mirroring_repositories do |mirror_settings|
mirror_settings.update source_project_uri
end
end
# Check that the target project has the commit from the source
target_project.visit!
expect(page).to have_content('README.md')
expect(page).to have_content('This is a pull mirroring test project')
expect(page).to have_content("Mirrored from #{masked_url(source_project_uri)}")
end
def masked_url(url)
url.user = '*****'
url
end
end
end
end
...@@ -4,6 +4,12 @@ module QA ...@@ -4,6 +4,12 @@ module QA
module Support module Support
module Page module Page
module Logging module Logging
def assert_no_element(name)
log("asserting no element :#{name}")
super
end
def refresh def refresh
log("refreshing #{current_url}") log("refreshing #{current_url}")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment