Commit cbb92929 authored by Andrejs Cunskis's avatar Andrejs Cunskis Committed by Mark Lapierre

Update all resource attributes when calling reload!

Refactor QA::Resource::Base class

Add ability to update existing attribute values on reload! call

Seperate class method declaration for better readability

Add attributes method for shorter multiple attribute declaration

Add init method to create new instance of class without performing
fabrication

Fix custom attribute tracking

Add reload capability to MergeRequest resource

Update id to iid to be in line with api response

Remove custom reload! overload

Add mr comments validation to github importer spec

Remove unnecessary evaluator

Remove attribute reference in initialize block

Remove unnecessary require of blank

Fix yard doc comments

Remove attribute method tracking via unused method variable

Refactor Resources to use init method instead of new.tap

Use more up to date variant of calling unbound method
parent f81cc1f1
......@@ -67,7 +67,7 @@ module QA
end
def sign_in_using_admin_credentials
admin = QA::Resource::User.new.tap do |user|
admin = QA::Resource::User.init do |user|
user.username = QA::Runtime::User.admin_username
user.password = QA::Runtime::User.admin_password
end
......
# frozen_string_literal: true
require 'forwardable'
require 'capybara/dsl'
require 'active_support/core_ext/array/extract_options'
module QA
module Resource
class Base
extend SingleForwardable
include ApiFabricator
extend Capybara::DSL
NoValueError = Class.new(RuntimeError)
def_delegators :evaluator, :attribute
class << self
# Initialize new instance of class without fabrication
#
# @param [Proc] prepare_block
def init(&prepare_block)
new.tap(&prepare_block)
end
def self.fabricate!(*args, &prepare_block)
fabricate_via_api!(*args, &prepare_block)
rescue NotImplementedError
fabricate_via_browser_ui!(*args, &prepare_block)
end
def fabricate!(*args, &prepare_block)
fabricate_via_api!(*args, &prepare_block)
rescue NotImplementedError
fabricate_via_browser_ui!(*args, &prepare_block)
end
def self.fabricate_via_browser_ui!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
def fabricate_via_browser_ui!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
current_url
current_url
end
end
end
def self.fabricate_via_api!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
def fabricate_via_api!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
raise NotImplementedError unless resource.api_support?
resource.eager_load_api_client!
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
end
end
def remove_via_api!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
resource.eager_load_api_client!
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.remove_via_api! }
end
end
raise NotImplementedError unless resource.api_support?
private
resource.eager_load_api_client!
def do_fabricate!(resource:, prepare_block:, parents: [])
prepare_block.call(resource) if prepare_block
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
resource_web_url = yield
resource.web_url = resource_web_url
resource
end
def log_fabrication(method, resource, parents, args)
return yield unless Runtime::Env.debug?
start = Time.now
prefix = "==#{'=' * parents.size}>"
msg = [prefix]
msg << "Built a #{name}"
msg << "as a dependency of #{parents.last}" if parents.any?
msg << "via #{method}"
yield.tap do
msg << "in #{Time.now - start} seconds"
puts msg.join(' ')
puts if parents.empty?
end
end
# Define custom attribute
#
# @param [Symbol] name
# @return [void]
def attribute(name, &block)
(@attribute_names ||= []).push(name) # save added attributes
attr_writer(name)
define_method(name) do
instance_variable_get("@#{name}") || instance_variable_set("@#{name}", populate_attribute(name, block))
end
end
# Define multiple custom attributes
#
# @param [Array] names
# @return [void]
def attributes(*names)
names.each { |name| attribute(name) }
end
end
def self.remove_via_api!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
# Override api reload! and update custom attributes from api_resource
#
api_reload = instance_method(:reload!)
define_method(:reload!) do
api_reload.bind_call(self)
return self unless api_resource
resource.eager_load_api_client!
all_attributes.each do |attribute_name|
api_value = api_resource[attribute_name]
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.remove_via_api! }
instance_variable_set("@#{attribute_name}", api_value) if api_value
end
self
end
attribute :web_url
def fabricate!(*_args)
raise NotImplementedError
end
def visit!
Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"])
Runtime::Logger.debug(%(Visiting #{self.class.name} at "#{web_url}"))
# Just in case an async action is not yet complete
Support::WaitForRequests.wait_for_requests
......@@ -78,14 +151,12 @@ module QA
Support::WaitForRequests.wait_for_requests
end
def populate(*attributes)
attributes.each(&method(:public_send))
def populate(*attribute_names)
attribute_names.each { |attribute_name| public_send(attribute_name) }
end
def wait_until(max_duration: 60, sleep_interval: 0.1)
QA::Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval) do
yield
end
def wait_until(max_duration: 60, sleep_interval: 0.1, &block)
QA::Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval, &block)
end
private
......@@ -101,70 +172,27 @@ module QA
def attribute_value(name, block)
api_value = api_resource&.dig(name)
if api_value && block
log_having_both_api_result_and_block(name, api_value)
end
log_having_both_api_result_and_block(name, api_value) if api_value && block
api_value || (block && instance_exec(&block))
end
def log_having_both_api_result_and_block(name, api_value)
QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
# Get all defined attributes across all parents
#
# @return [Array<Symbol>]
def all_attributes
@all_attributes ||= self.class.ancestors
.select { |clazz| clazz <= QA::Resource::Base }
.map { |clazz| clazz.instance_variable_get(:@attribute_names) }
.flatten
.compact
end
def self.do_fabricate!(resource:, prepare_block:, parents: [])
prepare_block.call(resource) if prepare_block
resource_web_url = yield
resource.web_url = resource_web_url
resource
end
private_class_method :do_fabricate!
def self.log_fabrication(method, resource, parents, args)
return yield unless Runtime::Env.debug?
start = Time.now
prefix = "==#{'=' * parents.size}>"
msg = [prefix]
msg << "Built a #{name}"
msg << "as a dependency of #{parents.last}" if parents.any?
msg << "via #{method}"
yield.tap do
msg << "in #{Time.now - start} seconds"
puts msg.join(' ')
puts if parents.empty?
end
end
private_class_method :log_fabrication
def self.evaluator
@evaluator ||= Base::DSL.new(self)
end
private_class_method :evaluator
class DSL
def initialize(base)
@base = base
end
def attribute(name, &block)
@base.module_eval do
attr_writer(name)
define_method(name) do
instance_variable_get("@#{name}") ||
instance_variable_set(
"@#{name}",
populate_attribute(name, block))
end
end
end
def log_having_both_api_result_and_block(name, api_value)
QA::Runtime::Logger.info(<<~MSG.strip)
<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored.
MSG
end
attribute :web_url
end
end
end
......@@ -3,7 +3,7 @@
module QA
module Resource
class Group < GroupBase
attr_accessor :description
attributes :require_two_factor_authentication, :description
attribute :full_path do
determine_full_path
......@@ -15,8 +15,6 @@ module QA
end
end
attribute :require_two_factor_authentication
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
......
......@@ -9,17 +9,17 @@ module QA
attr_accessor :path
attribute :id
attribute :runners_token
attribute :name
attribute :full_path
attributes :id,
:runners_token,
:name,
:full_path
# Get group labels
#
# @return [Array<QA::Resource::GroupLabel>]
def labels
parse_body(api_get_from("#{api_get_path}/labels")).map do |label|
GroupLabel.new.tap do |resource|
GroupLabel.init do |resource|
resource.api_client = api_client
resource.group = self
resource.id = label[:id]
......
# frozen_string_literal: true
require 'securerandom'
require 'active_support/core_ext/object/blank'
module QA
module Resource
class MergeRequest < Base
attr_accessor :approval_rules,
:id,
:title,
:description,
:source_branch,
:target_branch,
:target_new_branch,
:assignee,
:milestone,
......@@ -22,9 +17,12 @@ module QA
:wait_for_merge,
:template
attribute :merge_when_pipeline_succeeds
attribute :merge_status
attribute :state
attributes :iid,
:title,
:description,
:merge_when_pipeline_succeeds,
:merge_status,
:state
attribute :project do
Project.fabricate! do |resource|
......@@ -32,11 +30,15 @@ module QA
end
end
attribute :target_branch do
project.default_branch
end
attribute :target do
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = target_branch
resource.new_branch = @target_new_branch
resource.new_branch = target_new_branch
resource.remote_branch = target_branch
end
end
......@@ -62,7 +64,6 @@ module QA
@labels = []
@file_name = "added_file-#{SecureRandom.hex(8)}.txt"
@file_content = "File Added"
@target_branch = project.default_branch
@target_new_branch = true
@no_preparation = false
@wait_for_merge = true
......@@ -89,21 +90,19 @@ module QA
end
def fabricate_via_api!
raise ResourceNotFoundError unless id
resource_web_url(api_get)
rescue ResourceNotFoundError
rescue ResourceNotFoundError, NoValueError # rescue if iid not populated
populate_target_and_source_if_required
super
end
def api_merge_path
"/projects/#{project.id}/merge_requests/#{id}/merge"
"/projects/#{project.id}/merge_requests/#{iid}/merge"
end
def api_get_path
"/projects/#{project.id}/merge_requests/#{id}"
"/projects/#{project.id}/merge_requests/#{iid}"
end
def api_post_path
......@@ -112,18 +111,22 @@ module QA
def api_post_body
{
description: @description,
source_branch: @source_branch,
target_branch: @target_branch,
title: @title
description: description,
source_branch: source_branch,
target_branch: target_branch,
title: title
}
end
def api_comments_path
"#{api_get_path}/notes"
end
def merge_via_api!
Support::Waiter.wait_until(sleep_interval: 1) do
QA::Runtime::Logger.debug("Waiting until merge request with id '#{id}' can be merged")
QA::Runtime::Logger.debug("Waiting until merge request with id '#{iid}' can be merged")
reload!.api_resource[:merge_status] == 'can_be_merged'
reload!.merge_status == 'can_be_merged'
end
Support::Retrier.retry_on_exception do
......@@ -141,12 +144,12 @@ module QA
end
end
def reload!
# Refabricate so that we can return a new object with updated attributes
self.class.fabricate_via_api! do |resource|
resource.project = project
resource.id = api_resource[:iid]
end
# Get MR comments
#
# @return [Array]
def comments
response = get(Runtime::API::Request.new(api_client, api_comments_path).url)
parse_body(response)
end
private
......@@ -158,8 +161,6 @@ module QA
end
def populate_target_and_source_if_required
@target_branch ||= project.default_branch
populate(:target, :source) unless @no_preparation
end
end
......
......@@ -25,7 +25,7 @@ module QA
end
def self.default
Resource::User.new.tap do |user|
Resource::User.init do |user|
user.username = Runtime::User.ldap_user? ? Runtime::User.ldap_username : Runtime::User.username
user.password = Runtime::User.ldap_user? ? Runtime::User.ldap_password : Runtime::User.password
end
......
......@@ -9,7 +9,7 @@ module QA
end
def setup
@k3s = Service::DockerRun::K3s.new.tap do |k3s|
@k3s = Service::DockerRun::K3s.init do |k3s|
k3s.remove!
k3s.register!
......
......@@ -64,7 +64,7 @@ module QA
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.id = merge_request[:iid]
mr.iid = merge_request[:iid]
end
expect(merge_request.state).to eq('opened')
......@@ -109,7 +109,7 @@ module QA
merge_request = Support::Waiter.wait_until(sleep_interval: 5) do
mr = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.id = merge_request[:iid]
mr.iid = merge_request[:iid]
end
next unless mr.state == 'merged'
......
......@@ -34,7 +34,7 @@ module QA
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.id = merge_request[:iid]
mr.iid = merge_request[:iid]
end.merge_via_api!
expect(merge_request[:state]).to eq('merged')
......
......@@ -55,7 +55,7 @@ module QA
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.id = merge_request[:iid]
mr.iid = merge_request[:iid]
end.merge_via_api!
expect(merge_request[:state]).to eq('merged')
......
......@@ -39,7 +39,7 @@ module QA
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.id = merge_request[:iid]
mr.iid = merge_request[:iid]
end.merge_via_api!
expect(merge_request[:state]).to eq('merged')
......
......@@ -17,8 +17,8 @@ module QA
end
let(:registry) do
Resource::RegistryRepository.new.tap do |repository|
repository.name = "#{project.path_with_namespace}"
Resource::RegistryRepository.init do |repository|
repository.name = project.path_with_namespace
repository.project = project
repository.tag_name = 'master'
end
......
......@@ -36,7 +36,7 @@ module QA
end
let(:imported_group) do
Resource::Group.new.tap do |group|
Resource::Group.init do |group|
group.api_client = api_client
group.sandbox = sandbox
group.path = source_group.path
......@@ -44,7 +44,7 @@ module QA
end
let(:imported_subgroup) do
Resource::Group.new.tap do |group|
Resource::Group.init do |group|
group.api_client = api_client
group.sandbox = imported_group
group.path = subgroup.path
......
......@@ -6,7 +6,7 @@ module QA
RSpec.describe 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do
describe 'Check for broken images', :requires_admin do
before(:context) do
admin = QA::Resource::User.new.tap do |user|
admin = QA::Resource::User.init do |user|
user.username = QA::Runtime::User.admin_username
user.password = QA::Runtime::User.admin_password
end
......
......@@ -105,9 +105,15 @@ module QA
def verify_merge_requests_import
merge_requests = imported_project.merge_requests
merge_request = Resource::MergeRequest.init do |mr|
mr.project = imported_project
mr.iid = merge_requests.first[:iid]
mr.api_client = api_client
end.reload!
mr_comments = merge_request.comments.map { |comment| comment[:body] } # rubocop:disable Rails/Pluck
expect(merge_requests.length).to eq(1)
expect(merge_requests.first).to include(
expect(merge_request.api_resource).to include(
title: 'Improve readme',
state: 'opened',
target_branch: 'main',
......@@ -117,6 +123,13 @@ module QA
*Created by: gitlab-qa-github*\n\nThis improves the README file a bit.\r\n\r\nTODO:\r\n\r\n \r\n\r\n- [ ] Do foo\r\n- [ ] Make bar\r\n - [ ] Think about baz
DSC
)
expect(mr_comments).to eq(
[
"*Created by: gitlab-qa-github*\n\n[PR comment by @sliaquat] Nice work! ",
"*Created by: gitlab-qa-github*\n\n[Single diff comment] Nice addition",
"*Created by: gitlab-qa-github*\n\n[Single diff comment] Good riddance"
]
)
end
end
end
......
......@@ -94,7 +94,7 @@ module QA
end
def run_jenkins_server
Service::DockerRun::Jenkins.new.tap do |runner|
Service::DockerRun::Jenkins.init do |runner|
runner.pull
runner.register!
end
......
......@@ -8,7 +8,7 @@ module QA
access_token = Resource::PersonalAccessToken.fabricate!.token
user = Resource::User.new.tap do |user|
user = Resource::User.init do |user|
user.username = Runtime::User.username
user.password = access_token
end
......
......@@ -14,7 +14,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = "my_package-#{SecureRandom.hex(4)}"
package.project = project
end
......
......@@ -12,7 +12,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = 'conantest'
package.project = project
end
......
......@@ -10,7 +10,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = "my_package"
package.project = project
end
......
......@@ -24,7 +24,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = package_name
package.project = project
end
......
......@@ -33,7 +33,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = package_name
package.project = project
end
......
......@@ -50,7 +50,7 @@ module QA
stages:
- deploy
deploy:
stage: deploy
script:
......@@ -72,7 +72,7 @@ module QA
stages:
- install
install:
stage: install
script:
......@@ -120,7 +120,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = "@#{registry_scope}/#{project.name}"
package.project = project
end
......
......@@ -14,7 +14,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = "dotnetcore-#{SecureRandom.hex(8)}"
package.project = project
end
......
......@@ -11,7 +11,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = 'mypypipackage'
package.project = project
end
......
......@@ -12,7 +12,7 @@ module QA
end
let(:package) do
Resource::Package.new.tap do |package|
Resource::Package.init do |package|
package.name = 'mygem'
package.project = project
end
......
......@@ -16,7 +16,7 @@ module QA
# The user that signs in via the IDP with username `user3` and password `user3pass`
# will have `user_3` as username in GitLab
let(:user) do
QA::Resource::User.new.tap do |user|
QA::Resource::User.init do |user|
user.username = 'user_3'
user.email = 'user_3@example.com'
user.name = 'User Three'
......
......@@ -22,7 +22,7 @@ module QA
context 'Failed sign in', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/736' do
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
invalid_user = QA::Resource::User.new.tap do |user|
invalid_user = Resource::User.init do |user|
user.username = 'bad_user_name'
user.password = 'bad_pasword'
end
......
......@@ -114,7 +114,7 @@ module QA
end
it 'rejects non-member users', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1778', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/224465', type: :investigating } do
non_member_user = Resource::User.new.tap do |user|
non_member_user = Resource::User.init do |user|
user.username = ''
user.password = ''
user.name = 'non_member_user'
......@@ -205,7 +205,7 @@ module QA
user.password = Runtime::User.password
end
@root = Resource::User.new.tap do |user|
@root = Resource::User.init do |user|
user.username = 'root'
user.name = 'GitLab QA'
user.email = 'root@gitlab.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