base.rb 4.05 KB
Newer Older
1 2
# frozen_string_literal: true

3
require 'forwardable'
4
require 'capybara/dsl'
5

6 7 8
module QA
  module Factory
    class Base
9
      extend SingleForwardable
10 11
      include ApiFabricator
      extend Capybara::DSL
12

13 14 15
      NoValueError = Class.new(RuntimeError)

      def_delegators :evaluator, :attribute
16

17 18 19 20
      def fabricate!(*_args)
        raise NotImplementedError
      end

21 22 23 24
      def visit!
        visit(web_url)
      end

25 26 27 28
      def populate(*attributes)
        attributes.each(&method(:public_send))
      end

29 30 31 32 33
      private

      def populate_attribute(name, block)
        value = attribute_value(name, block)

34
        raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

        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

53 54 55 56 57
      def self.fabricate!(*args, &prepare_block)
        fabricate_via_api!(*args, &prepare_block)
      rescue NotImplementedError
        fabricate_via_browser_ui!(*args, &prepare_block)
      end
58

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
      def self.fabricate_via_browser_ui!(*args, &prepare_block)
        options = args.extract_options!
        factory = options.fetch(:factory) { new }
        parents = options.fetch(:parents) { [] }

        do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do
          log_fabrication(:browser_ui, factory, parents, args) { factory.fabricate!(*args) }

          current_url
        end
      end

      def self.fabricate_via_api!(*args, &prepare_block)
        options = args.extract_options!
        factory = options.fetch(:factory) { new }
        parents = options.fetch(:parents) { [] }

        raise NotImplementedError unless factory.api_support?

        factory.eager_load_api_client!

        do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do
          log_fabrication(:api, factory, parents, args) { factory.fabricate_via_api! }
        end
      end

      def self.do_fabricate!(factory:, prepare_block:, parents: [])
        prepare_block.call(factory) if prepare_block

        resource_web_url = yield
89
        factory.web_url = resource_web_url
90

91
        factory
92 93 94 95
      end
      private_class_method :do_fabricate!

      def self.log_fabrication(method, factory, parents, args)
Mark Lapierre's avatar
Mark Lapierre committed
96
        return yield unless Runtime::Env.debug?
97

98 99 100 101 102 103
        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} with args #{args}"
104

105 106 107 108
        yield.tap do
          msg << "in #{Time.now - start} seconds"
          puts msg.join(' ')
          puts if parents.empty?
109 110
        end
      end
111
      private_class_method :log_fabrication
112

113 114
      def self.evaluator
        @evaluator ||= Factory::Base::DSL.new(self)
115
      end
116
      private_class_method :evaluator
117

118 119 120 121 122 123 124 125 126
      def self.dynamic_attributes
        const_get(:DynamicAttributes)
      rescue NameError
        mod = const_set(:DynamicAttributes, Module.new)

        include mod

        mod
      end
127

128 129 130 131 132
      def self.attributes_names
        dynamic_attributes.instance_methods(false).sort.grep_v(/=$/)
      end

      class DSL
133 134 135 136
        def initialize(base)
          @base = base
        end

137 138 139
        def attribute(name, &block)
          @base.dynamic_attributes.module_eval do
            attr_writer(name)
140

141 142 143 144 145
            define_method(name) do
              instance_variable_get("@#{name}") ||
                instance_variable_set(
                  "@#{name}",
                  populate_attribute(name, block))
146
            end
147 148
          end
        end
149
      end
150 151

      attribute :web_url
152 153 154
    end
  end
end