File: linter.rb

package info (click to toggle)
ruby-factory-bot 6.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,372 kB
  • sloc: ruby: 7,827; makefile: 6
file content (121 lines) | stat: -rw-r--r-- 2,806 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
module FactoryBot
  class Linter
    def initialize(factories, strategy: :create, traits: false, verbose: false)
      @factories_to_lint = factories
      @factory_strategy = strategy
      @traits = traits
      @verbose = verbose
      @invalid_factories = calculate_invalid_factories
    end

    def lint!
      if invalid_factories.any?
        raise InvalidFactoryError, error_message
      end
    end

    private

    attr_reader :factories_to_lint, :invalid_factories, :factory_strategy

    def calculate_invalid_factories
      factories_to_lint.each_with_object(Hash.new([])) do |factory, result|
        errors = lint(factory)
        result[factory] |= errors unless errors.empty?
      end
    end

    class FactoryError
      def initialize(wrapped_error, factory)
        @wrapped_error = wrapped_error
        @factory = factory
      end

      def message
        message = @wrapped_error.message
        "* #{location} - #{message} (#{@wrapped_error.class.name})"
      end

      def verbose_message
        <<~MESSAGE
          #{message}
            #{@wrapped_error.backtrace.join("\n  ")}
        MESSAGE
      end

      def location
        @factory.name
      end
    end

    class FactoryTraitError < FactoryError
      def initialize(wrapped_error, factory, trait_name)
        super(wrapped_error, factory)
        @trait_name = trait_name
      end

      def location
        "#{@factory.name}+#{@trait_name}"
      end
    end

    def lint(factory)
      if @traits
        lint_factory(factory) + lint_traits(factory)
      else
        lint_factory(factory)
      end
    end

    def lint_factory(factory)
      result = []
      begin
        in_transaction { FactoryBot.public_send(factory_strategy, factory.name) }
      rescue => e
        result |= [FactoryError.new(e, factory)]
      end
      result
    end

    def lint_traits(factory)
      result = []
      factory.definition.defined_traits.map(&:name).each do |trait_name|
        in_transaction { FactoryBot.public_send(factory_strategy, factory.name, trait_name) }
      rescue => e
        result |= [FactoryTraitError.new(e, factory, trait_name)]
      end
      result
    end

    def error_message
      lines = invalid_factories.map { |_factory, exceptions|
        exceptions.map(&error_message_type)
      }.flatten

      <<~ERROR_MESSAGE.strip
        The following factories are invalid:

        #{lines.join("\n")}
      ERROR_MESSAGE
    end

    def error_message_type
      if @verbose
        :verbose_message
      else
        :message
      end
    end

    def in_transaction
      if defined?(ActiveRecord::Base)
        ActiveRecord::Base.transaction do
          yield
          raise ActiveRecord::Rollback
        end
      else
        yield
      end
    end
  end
end