Commit d578616c authored by Mark Lapierre's avatar Mark Lapierre Committed by Walmyr Lima e Silva Filho

Add QA test of fork creation after storage change

Tests that if a fork is created and then the parent project is moved
to a new gitaly storage/shard, another fork can be created after the
move.

See https://gitlab.com/gitlab-org/gitlab/issues/38264

Add the ability to create a fork via the API, check import status (to
confirm when the fork is complete), and change repository storage.

Resource::Fork needed to be updated, which also required updating
Resource::MergeRequestFromFork and the test that uses it so that that
test can still fabricate a fork via the UI.
parent 0357dce1
...@@ -71,7 +71,12 @@ export default { ...@@ -71,7 +71,12 @@ export default {
<template> <template>
<div class="tree-content-holder"> <div class="tree-content-holder">
<div class="table-holder bordered-box"> <div class="table-holder bordered-box">
<table :aria-label="tableCaption" class="table tree-table qa-file-tree" aria-live="polite"> <table
:aria-label="tableCaption"
class="table tree-table"
aria-live="polite"
data-qa-selector="file_tree_table"
>
<table-header v-once /> <table-header v-once />
<tbody> <tbody>
<parent-row <parent-row
......
...@@ -139,7 +139,13 @@ export default { ...@@ -139,7 +139,13 @@ export default {
class="d-inline-block align-text-bottom fa-fw" class="d-inline-block align-text-bottom fa-fw"
/> />
<i v-else :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> <i v-else :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i>
<component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> <component
:is="linkComponent"
:to="routerLinkTo"
:href="url"
class="str-truncated"
data-qa-selector="file_name_link"
>
{{ fullPath }} {{ fullPath }}
</component> </component>
<!-- eslint-disable-next-line @gitlab/vue-i18n/no-bare-strings --> <!-- eslint-disable-next-line @gitlab/vue-i18n/no-bare-strings -->
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64) = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64)
.d-flex.flex-column.flex-wrap.align-items-baseline .d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline .d-inline-flex.align-items-baseline
%h1.home-panel-title.prepend-top-8.append-bottom-5.qa-project-name %h1.home-panel-title.prepend-top-8.append-bottom-5{ data: { qa_selector: 'project_name_content' } }
= @project.name = @project.name
%span.visibility-icon.text-secondary.prepend-left-4.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) } %span.visibility-icon.text-secondary.prepend-left-4.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'}) = visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
- source = visible_fork_source(@project) - source = visible_fork_source(@project)
- if source - if source
#{ s_('ForkedFromProjectPath|Forked from') } #{ s_('ForkedFromProjectPath|Forked from') }
= link_to source.full_name, project_path(source) = link_to source.full_name, project_path(source), data: { qa_selector: 'forked_from_link' }
- else - else
= s_('ForkedFromProjectPath|Forked from an inaccessible project') = s_('ForkedFromProjectPath|Forked from an inaccessible project')
......
.tree-content-holder.js-tree-content{ data: tree_content_data(@logs_path, @project, @path) } .tree-content-holder.js-tree-content{ data: tree_content_data(@logs_path, @project, @path) }
.table-holder.bordered-box .table-holder.bordered-box
%table.table#tree-slider{ class: "table_#{@hex_path} tree-table qa-file-tree" } %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
%thead %thead
%tr %tr
%th= s_('ProjectFileTree|Name') %th= s_('ProjectFileTree|Name')
......
...@@ -7,6 +7,14 @@ module QA ...@@ -7,6 +7,14 @@ module QA
include Page::Component::ClonePanel include Page::Component::ClonePanel
include Page::Project::SubMenus::Settings include Page::Project::SubMenus::Settings
view 'app/assets/javascripts/repository/components/table/row.vue' do
element :file_name_link
end
view 'app/assets/javascripts/repository/components/table/index.vue' do
element :file_tree_table
end
view 'app/views/layouts/header/_new_dropdown.haml' do view 'app/views/layouts/header/_new_dropdown.haml' do
element :new_menu_toggle element :new_menu_toggle
element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern
...@@ -17,7 +25,8 @@ module QA ...@@ -17,7 +25,8 @@ module QA
end end
view 'app/views/projects/_home_panel.html.haml' do view 'app/views/projects/_home_panel.html.haml' do
element :project_name element :forked_from_link
element :project_name_content
end end
view 'app/views/projects/_files.html.haml' do view 'app/views/projects/_files.html.haml' do
...@@ -37,10 +46,6 @@ module QA ...@@ -37,10 +46,6 @@ module QA
element :quick_actions element :quick_actions
end end
view 'app/views/projects/tree/_tree_content.html.haml' do
element :file_tree
end
view 'app/views/projects/tree/_tree_header.html.haml' do view 'app/views/projects/tree/_tree_header.html.haml' do
element :add_to_tree element :add_to_tree
element :new_file_option element :new_file_option
...@@ -79,14 +84,18 @@ module QA ...@@ -79,14 +84,18 @@ module QA
click_on 'Fork' click_on 'Fork'
end end
def forked_from?(parent_project_name)
has_element?(:forked_from_link, text: parent_project_name)
end
def click_file(filename) def click_file(filename)
within_element(:file_tree) do within_element(:file_tree_table) do
click_on filename click_on filename
end end
end end
def click_commit(commit_msg) def click_commit(commit_msg)
within_element(:file_tree) do within_element(:file_tree_table) do
click_on commit_msg click_on commit_msg
end end
end end
...@@ -96,6 +105,16 @@ module QA ...@@ -96,6 +105,16 @@ module QA
click_link 'New issue' click_link 'New issue'
end end
def has_file?(name)
within_element(:file_tree_table) do
has_element?(:file_name_link, text: name)
end
end
def has_name?(name)
has_element?(:project_name_content, text: name)
end
def last_commit_content def last_commit_content
find_element(:commit_content).text find_element(:commit_content).text
end end
...@@ -113,7 +132,7 @@ module QA ...@@ -113,7 +132,7 @@ module QA
end end
def project_name def project_name
find('.qa-project-name').text find_element(:project_name_content).text
end end
def switch_to_branch(branch_name) def switch_to_branch(branch_name)
......
...@@ -8,10 +8,12 @@ module QA ...@@ -8,10 +8,12 @@ module QA
module ApiFabricator module ApiFabricator
include Capybara::DSL include Capybara::DSL
ResourceNotFoundError = Class.new(RuntimeError)
ResourceFabricationFailedError = Class.new(RuntimeError) ResourceFabricationFailedError = Class.new(RuntimeError)
ResourceURLMissingError = Class.new(RuntimeError)
ResourceNotDeletedError = Class.new(RuntimeError) ResourceNotDeletedError = Class.new(RuntimeError)
ResourceNotFoundError = Class.new(RuntimeError)
ResourceQueryError = Class.new(RuntimeError)
ResourceUpdateFailedError = Class.new(RuntimeError)
ResourceURLMissingError = Class.new(RuntimeError)
attr_reader :api_resource, :api_response attr_reader :api_resource, :api_response
attr_writer :api_client attr_writer :api_client
......
...@@ -3,19 +3,24 @@ ...@@ -3,19 +3,24 @@
module QA module QA
module Resource module Resource
class Fork < Base class Fork < Base
attribute :name do
upstream.name
end
attribute :project do attribute :project do
Resource::Project.fabricate! do |resource| Resource::Project.fabricate_via_api! do |resource|
resource.name = upstream.project.name resource.add_name_uuid = false
resource.path_with_namespace = "#{user.name}/#{upstream.project.name}" resource.name = name
resource.path_with_namespace = "#{user.username}/#{name}"
end end
end end
attribute :upstream do attribute :upstream do
Repository::ProjectPush.fabricate! Repository::ProjectPush.fabricate!.project
end end
attribute :user do attribute :user do
User.fabricate! do |resource| User.fabricate_via_api! do |resource|
if Runtime::Env.forker? if Runtime::Env.forker?
resource.username = Runtime::Env.forker_username resource.username = Runtime::Env.forker_username
resource.password = Runtime::Env.forker_password resource.password = Runtime::Env.forker_password
...@@ -33,7 +38,7 @@ module QA ...@@ -33,7 +38,7 @@ module QA
login.sign_in_using_credentials(user: user) login.sign_in_using_credentials(user: user)
end end
upstream.project.visit! upstream.visit!
Page::Project::Show.perform(&:fork_project) Page::Project::Show.perform(&:fork_project)
...@@ -47,6 +52,41 @@ module QA ...@@ -47,6 +52,41 @@ module QA
populate(:project) populate(:project)
end end
def fabricate_via_api!
populate(:upstream, :user)
Runtime::Logger.debug("Forking project #{upstream.name} to namespace #{user.username}...")
super
wait_until_forked
populate(:project)
end
def api_get_path
"/projects/#{CGI.escape(path_with_namespace)}"
end
def api_post_path
"/projects/#{upstream.id}/fork"
end
def api_post_body
{
namespace: user.username,
name: name,
path: name
}
end
def wait_until_forked
Runtime::Logger.debug("Waiting for the fork process to complete...")
forked = wait_until do
project.import_status == "finished"
end
raise "Timed out while waiting for the fork process to complete." unless forked
end
end end
end end
end end
...@@ -8,7 +8,7 @@ module QA ...@@ -8,7 +8,7 @@ module QA
attr_accessor :fork_branch attr_accessor :fork_branch
attribute :fork do attribute :fork do
Fork.fabricate! Fork.fabricate_via_browser_ui!
end end
attribute :push do attribute :push do
......
...@@ -94,6 +94,10 @@ module QA ...@@ -94,6 +94,10 @@ module QA
"#{api_get_path}/runners" "#{api_get_path}/runners"
end end
def api_put_path
"/projects/#{id}"
end
def api_post_path def api_post_path
'/projects' '/projects'
end end
...@@ -115,6 +119,35 @@ module QA ...@@ -115,6 +119,35 @@ module QA
post_body post_body
end end
def change_repository_storage(new_storage)
put_body = { repository_storage: new_storage }
response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body
unless response.code == HTTP_STATUS_OK
raise ResourceUpdateFailedError, "Could not change repository storage to #{new_storage}. Request returned (#{response.code}): `#{response}`."
end
wait_until do
reload!
api_response[:repository_storage] == new_storage
end
end
def import_status
response = get Runtime::API::Request.new(api_client, "/projects/#{id}/import").url
unless response.code == HTTP_STATUS_OK
raise ResourceQueryError, "Could not get import status. Request returned (#{response.code}): `#{response}`."
end
result = parse_body(response)
Runtime::Logger.error("Import failed: #{result[:import_error]}") if result[:import_status] == "failed"
result[:import_status]
end
def runners(tag_list: nil) def runners(tag_list: nil)
response = get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url response = get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url
parse_body(response) parse_body(response)
......
...@@ -22,6 +22,10 @@ module QA ...@@ -22,6 +22,10 @@ module QA
SUPPORTED_FEATURES SUPPORTED_FEATURES
end end
def additional_repository_storage
ENV['QA_ADDITIONAL_REPOSITORY_STORAGE']
end
def admin_password def admin_password
ENV['GITLAB_ADMIN_PASSWORD'] ENV['GITLAB_ADMIN_PASSWORD']
end end
......
...@@ -6,7 +6,7 @@ module QA ...@@ -6,7 +6,7 @@ module QA
it 'user forks a project, submits a merge request and maintainer merges it' do it 'user forks a project, submits a merge request and maintainer merges it' do
Flow::Login.sign_in Flow::Login.sign_in
merge_request = Resource::MergeRequestFromFork.fabricate! do |merge_request| merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
merge_request.fork_branch = 'feature-branch' merge_request.fork_branch = 'feature-branch'
end end
......
# frozen_string_literal: true
module QA
context 'Create' do
describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin, quarantine: { type: :new } do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:parent_project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'parent-project'
project.initialize_with_readme = true
end
end
let(:fork_project) do
Resource::Fork.fabricate_via_api! do |fork|
fork.user = user
fork.upstream = parent_project
end.project
end
before do
parent_project.add_member(user)
end
it 'creates a 2nd fork after moving the parent project' do
Flow::Login.sign_in(as: user)
fork_project.visit!
parent_project.change_repository_storage(QA::Runtime::Env.additional_repository_storage)
second_fork_project = Resource::Fork.fabricate_via_api! do |fork|
fork.name = "second-fork"
fork.user = user
fork.upstream = parent_project
end.project
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = second_fork_project
push.file_name = 'new_file'
push.file_content = '# This is a new file'
push.commit_message = 'Add new file'
push.new_branch = false
end.project.visit!
Page::Project::Show.perform do |show|
expect(show).to have_file('new_file')
expect(show).to have_name(second_fork_project.name)
expect(show).to be_forked_from(parent_project.name)
end
end
end
end
end
...@@ -15,6 +15,7 @@ exports[`Repository table row component renders table row 1`] = ` ...@@ -15,6 +15,7 @@ exports[`Repository table row component renders table row 1`] = `
<a <a
class="str-truncated" class="str-truncated"
data-qa-selector="file_name_link"
href="https://test.com" href="https://test.com"
> >
...@@ -64,6 +65,7 @@ exports[`Repository table row component renders table row for path with special ...@@ -64,6 +65,7 @@ exports[`Repository table row component renders table row for path with special
<a <a
class="str-truncated" class="str-truncated"
data-qa-selector="file_name_link"
href="https://test.com" href="https://test.com"
> >
......
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