diff options
Diffstat (limited to 'qa/qa/resource/base.rb')
-rw-r--r-- | qa/qa/resource/base.rb | 218 |
1 files changed, 123 insertions, 95 deletions
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index 873ba353051..ca0087cf709 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -1,70 +1,143 @@ # 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 |