summaryrefslogtreecommitdiff
path: root/qa/qa/resource/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa/resource/base.rb')
-rw-r--r--qa/qa/resource/base.rb154
1 files changed, 154 insertions, 0 deletions
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
new file mode 100644
index 00000000000..f3eefb70520
--- /dev/null
+++ b/qa/qa/resource/base.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'forwardable'
+require 'capybara/dsl'
+
+module QA
+ module Resource
+ class Base
+ extend SingleForwardable
+ include ApiFabricator
+ extend Capybara::DSL
+
+ NoValueError = Class.new(RuntimeError)
+
+ def_delegators :evaluator, :attribute
+
+ def fabricate!(*_args)
+ raise NotImplementedError
+ end
+
+ def visit!
+ visit(web_url)
+ end
+
+ def populate(*attributes)
+ attributes.each(&method(:public_send))
+ end
+
+ private
+
+ def populate_attribute(name, block)
+ value = attribute_value(name, block)
+
+ raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value
+
+ value
+ end
+
+ 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
+
+ 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."
+ end
+
+ def self.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) { [] }
+
+ 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 self.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 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
+
+ def self.dynamic_attributes
+ const_get(:DynamicAttributes)
+ rescue NameError
+ mod = const_set(:DynamicAttributes, Module.new)
+
+ include mod
+
+ mod
+ end
+
+ def self.attributes_names
+ dynamic_attributes.instance_methods(false).sort.grep_v(/=$/)
+ end
+
+ class DSL
+ def initialize(base)
+ @base = base
+ end
+
+ def attribute(name, &block)
+ @base.dynamic_attributes.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
+ end
+
+ attribute :web_url
+ end
+ end
+end