195 lines
5.9 KiB
Ruby
195 lines
5.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'capybara/dsl'
|
|
require 'active_support/core_ext/array/extract_options'
|
|
|
|
module QA
|
|
module Resource
|
|
class Base
|
|
include ApiFabricator
|
|
extend Capybara::DSL
|
|
|
|
NoValueError = Class.new(RuntimeError)
|
|
|
|
class << self
|
|
# Initialize new instance of class without fabrication
|
|
#
|
|
# @param [Proc] prepare_block
|
|
def init(&prepare_block)
|
|
new.tap(&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 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) }
|
|
|
|
current_url
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
private
|
|
|
|
def 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
|
|
|
|
def log_fabrication(method, resource, parents, args)
|
|
start = Time.now
|
|
|
|
yield.tap do
|
|
Runtime::Logger.debug do
|
|
msg = ["==#{'=' * parents.size}>"]
|
|
msg << "Built a #{name}"
|
|
msg << "as a dependency of #{parents.last}" if parents.any?
|
|
msg << "via #{method}"
|
|
msg << "in #{Time.now - start} seconds"
|
|
|
|
msg.join(' ')
|
|
end
|
|
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
|
|
return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
|
|
|
|
instance_variable_set("@#{name}", attribute_value(name, block))
|
|
end
|
|
end
|
|
|
|
# Define multiple custom attributes
|
|
#
|
|
# @param [Array] names
|
|
# @return [void]
|
|
def attributes(*names)
|
|
names.each { |name| attribute(name) }
|
|
end
|
|
end
|
|
|
|
# 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
|
|
|
|
all_attributes.each do |attribute_name|
|
|
instance_variable_set("@#{attribute_name}", api_resource[attribute_name]) if api_resource.key?(attribute_name)
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
attribute :web_url
|
|
|
|
def fabricate!(*_args)
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def visit!
|
|
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
|
|
|
|
Support::Retrier.retry_until do
|
|
visit(web_url)
|
|
wait_until { current_url.include?(URI.parse(web_url).path.split('/').last || web_url) }
|
|
end
|
|
|
|
# Wait until the new page is ready for us to interact with it
|
|
Support::WaitForRequests.wait_for_requests
|
|
end
|
|
|
|
def populate(*attribute_names)
|
|
attribute_names.each { |attribute_name| public_send(attribute_name) }
|
|
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
|
|
|
|
def attribute_value(name, block)
|
|
no_api_value = !api_resource&.key?(name)
|
|
raise NoValueError, "No value was computed for #{name} of #{self.class.name}." if no_api_value && !block
|
|
|
|
unless no_api_value
|
|
api_value = api_resource[name]
|
|
log_having_both_api_result_and_block(name, api_value) if block
|
|
return api_value
|
|
end
|
|
|
|
instance_exec(&block)
|
|
end
|
|
|
|
# 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 log_having_both_api_result_and_block(name, api_value)
|
|
QA::Runtime::Logger.debug(<<~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
|
|
end
|
|
end
|
|
end
|