Commit f21ffd48 authored by Dan Davison's avatar Dan Davison

Merge branch 'eventually-matcher' into 'master'

E2E: Add waiter in bulk_import_group validation spec

See merge request gitlab-org/gitlab!62460
parents 20992f69 68d26367
......@@ -5,6 +5,10 @@ module QA
class Group < GroupBase
attr_accessor :description
attribute :full_path do
determine_full_path
end
attribute :sandbox do
Sandbox.fabricate_via_api! do |sandbox|
sandbox.api_client = api_client
......@@ -57,7 +61,7 @@ module QA
end
def api_get_path
"/groups/#{CGI.escape("#{determine_full_path}")}"
"/groups/#{CGI.escape(determine_full_path)}"
end
def api_post_body
......
......@@ -14,11 +14,11 @@ module QA
end
def api_post_path
"/groups/#{group.id}/labels"
"/groups/#{CGI.escape(group.full_path)}/labels"
end
def api_get_path
"/groups/#{group.id}/labels/#{id}"
"/groups/#{CGI.escape(group.full_path)}/labels/#{id}"
end
end
end
......
......@@ -11,6 +11,8 @@ module QA
@path = Runtime::Namespace.sandbox_name
end
alias_method :full_path, :path
def fabricate!
Page::Main::Menu.perform(&:go_to_groups)
......
......@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage', :requires_admin do
describe 'Group bulk import' do
describe 'Bulk group import' do
let!(:api_client) { Runtime::API::Client.as_admin }
let!(:user) do
Resource::User.fabricate_via_api! do |usr|
......@@ -39,7 +39,7 @@ module QA
group.api_client = api_client
group.sandbox = sandbox
group.path = source_group.path
end.reload!
end
end
let(:imported_subgroup) do
......@@ -47,7 +47,7 @@ module QA
group.api_client = api_client
group.sandbox = imported_group
group.path = subgroup.path
end.reload!
end
end
def staging?
......@@ -60,17 +60,6 @@ module QA
end
before do
Resource::GroupLabel.fabricate_via_api! do |label|
label.api_client = api_client
label.group = source_group
label.title = "source-group-#{SecureRandom.hex(4)}"
end
Resource::GroupLabel.fabricate_via_api! do |label|
label.api_client = api_client
label.group = subgroup
label.title = "subgroup-#{SecureRandom.hex(4)}"
end
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
source_group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
......@@ -79,19 +68,53 @@ module QA
Page::Group::New.new.connect_gitlab_instance(Runtime::Scenario.gitlab_address, personal_access_token)
end
# Non blocking issues:
# https://gitlab.com/gitlab-org/gitlab/-/issues/331252
it(
'imports group with subgroups',
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785',
quarantine: {
only: { job: 'relative_url' },
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/330344',
type: :bug
}
) do
Page::Group::BulkImport.perform do |import_page|
import_page.import_group(source_group.path, sandbox.path)
aggregate_failures do
expect(import_page).to have_imported_group(source_group.path, wait: 120)
expect { imported_group.reload! }.to eventually_eq(source_group).within(duration: 30)
expect { imported_subgroup.reload! }.to eventually_eq(subgroup).within(duration: 30)
end
end
end
it(
'performs bulk group import from another gitlab instance',
'imports group labels',
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785',
exclude: { job: ['ce:relative_url', 'ee:relative_url'] },
issue_1: "https://gitlab.com/gitlab-org/gitlab/-/issues/330344",
issue_2: "https://gitlab.com/gitlab-org/gitlab/-/issues/331252",
issue_3: "https://gitlab.com/gitlab-org/gitlab/-/issues/331704"
quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331704', type: :bug }
) do
Resource::GroupLabel.fabricate_via_api! do |label|
label.api_client = api_client
label.group = source_group
label.title = "source-group-#{SecureRandom.hex(4)}"
end
Resource::GroupLabel.fabricate_via_api! do |label|
label.api_client = api_client
label.group = subgroup
label.title = "subgroup-#{SecureRandom.hex(4)}"
end
Page::Group::BulkImport.perform do |import_page|
import_page.import_group(source_group.path, sandbox.path)
aggregate_failures do
expect(import_page).to have_imported_group(source_group.path, wait: 120)
expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10)
expect { imported_subgroup.labels }.to eventually_include(*subgroup.labels).within(duration: 10)
end
end
end
......
......@@ -28,14 +28,17 @@ module QA
end
it 'transfers a subgroup to another group',
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1724' do
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1724' do
Page::Group::Menu.perform(&:click_group_general_settings_item)
Page::Group::Settings::General.perform do |general|
general.transfer_group(target_group.path)
sub_group_for_transfer.sandbox = target_group
sub_group_for_transfer.reload!
end
expect(page).to have_text("Group '#{sub_group_for_transfer.path}' was successfully transferred.")
expect(page.driver.current_url).to include("#{target_group.path}/#{sub_group_for_transfer.path}")
expect(page.driver.current_url).to include(sub_group_for_transfer.full_path)
end
after do
......
# frozen_string_literal: true
# Rspec matcher with build in retry logic
#
# USAGE:
#
# Basic
# expect { Something.that.takes.time.to_appear }.to eventually_eq(expected_result)
# expect { Something.that.takes.time.to_appear }.not_to eventually_eq(expected_result)
#
# With duration and attempts override
# expect { Something.that.takes.time.to_appear }.to eventually_eq(expected_result).within(duration: 10, attempts: 5)
module Matchers
%w[
eq
be
include
be_truthy
be_falsey
be_empty
].each do |op|
RSpec::Matchers.define(:"eventually_#{op}") do |*expected|
chain(:within) do |options = {}|
@duration = options[:duration]
@attempts = options[:attempts]
end
def supports_block_expectations?
true
end
match { |actual| wait_and_check(actual, :default_expectation) }
match_when_negated { |actual| wait_and_check(actual, :when_negated_expectation) }
description do
"eventually #{operator_msg} #{expected.inspect}"
end
failure_message do
"#{e}:\nexpected to #{description}, last attempt was #{@result.nil? ? 'nil' : @result}"
end
failure_message_when_negated do
"#{e}:\nexpected not to #{description}, last attempt was #{@result.nil? ? 'nil' : @result}"
end
# Execute rspec expectation within retrier
#
# @param [Proc] actual
# @param [Symbol] expectation_name
# @return [Boolean]
def wait_and_check(actual, expectation_name)
QA::Support::Retrier.retry_until(
max_attempts: @attempts,
max_duration: @duration,
sleep_interval: 0.5
) do
public_send(expectation_name, actual)
rescue RSpec::Expectations::ExpectationNotMetError, QA::Resource::ApiFabricator::ResourceNotFoundError
false
end
rescue QA::Support::Repeater::RetriesExceededError, QA::Support::Repeater::WaitExceededError => e
@e = e
false
end
# Execute rspec expectation
#
# @param [Proc] actual
# @return [void]
def default_expectation(actual)
expect(result(&actual)).to public_send(*expectation_args)
end
# Execute negated rspec expectation
#
# @param [Proc] actual
# @return [void]
def when_negated_expectation(actual)
expect(result(&actual)).not_to public_send(*expectation_args)
end
# Result of actual block
#
# @return [Object]
def result
@result = yield
end
# Error message placeholder to indicate waiter did not fail properly
# This message should not appear under normal circumstances since it should
# always be assigned from repeater
#
# @return [String]
def e
@e ||= 'Waiter did not fail!'
end
# Operator message
#
# @return [String]
def operator_msg
case operator
when 'eq' then 'equal'
else operator
end
end
# Expect operator
#
# @return [String]
def operator
@operator ||= name.to_s.match(/eventually_(.+?)$/).to_a[1].to_s
end
# Expectation args
#
# @return [String, Array]
def expectation_args
if operator.include?('truthy') || operator.include?('falsey') || operator.include?('empty')
operator
elsif operator == "include" && expected.is_a?(Array)
[operator, *expected]
else
[operator, expected]
end
end
end
end
end
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