calificador 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -16
  3. data/TODO.md +16 -0
  4. data/calificador.gemspec +54 -0
  5. data/lib/calificador.rb +8 -4
  6. data/lib/calificador/assert.rb +15 -0
  7. data/lib/calificador/assertor.rb +79 -35
  8. data/lib/calificador/build/attribute_evaluator.rb +34 -30
  9. data/lib/calificador/build/basic_factory.rb +195 -0
  10. data/lib/calificador/build/mock_factory.rb +151 -0
  11. data/lib/calificador/build/object_factory.rb +85 -0
  12. data/lib/calificador/build/trait.rb +0 -20
  13. data/lib/calificador/context/basic_context.rb +406 -0
  14. data/lib/calificador/context/class_method_context.rb +0 -0
  15. data/lib/calificador/{spec → context}/condition_context.rb +1 -3
  16. data/lib/calificador/{spec/type_context.rb → context/instance_context.rb} +5 -10
  17. data/lib/calificador/context/operation_context.rb +27 -0
  18. data/lib/calificador/context/override/argument_override.rb +73 -0
  19. data/lib/calificador/context/override/basic_override.rb +14 -0
  20. data/lib/calificador/context/override/factory_override.rb +31 -0
  21. data/lib/calificador/context/override/property_override.rb +61 -0
  22. data/lib/calificador/context/test_environment.rb +283 -0
  23. data/lib/calificador/{spec → context}/test_method.rb +2 -31
  24. data/lib/calificador/{spec → context}/test_root.rb +3 -15
  25. data/lib/calificador/{spec/examine_context.rb → context/type_context.rb} +7 -10
  26. data/lib/calificador/key.rb +27 -15
  27. data/lib/calificador/minitest/minitest_patches.rb +0 -2
  28. data/lib/calificador/test.rb +1 -3
  29. data/lib/calificador/test_mixin.rb +143 -139
  30. data/lib/calificador/util/call_formatter.rb +5 -5
  31. data/lib/calificador/util/core_extensions.rb +104 -79
  32. data/lib/calificador/util/proxy_object.rb +63 -0
  33. data/lib/calificador/version.rb +1 -1
  34. metadata +22 -42
  35. data/lib/calificador/build/attribute_container.rb +0 -103
  36. data/lib/calificador/build/factory.rb +0 -132
  37. data/lib/calificador/spec/basic_context.rb +0 -353
  38. data/lib/calificador/spec/class_method_context.rb +0 -42
  39. data/lib/calificador/spec/instance_method_context.rb +0 -38
  40. data/lib/calificador/spec/test_environment.rb +0 -141
  41. data/lib/calificador/spec/value_override.rb +0 -37
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calificador
4
+ module Build
5
+ # Factory calss
6
+ class BasicFactory
7
+ # Configuration derived factories
8
+ class Dsl < Util::ProxyObject
9
+ attr_reader :__factory
10
+
11
+ def initialize(factory:)
12
+ super()
13
+
14
+ @__factory = factory
15
+ end
16
+
17
+ def add_attribute(name, **properties, &config)
18
+ type ||= __default_property_type(name: name)
19
+ attribute = Attribute.new(name: name, type: type, config: config)
20
+ @__factory.add_attribute(attribute)
21
+ end
22
+
23
+ def init_with(&block)
24
+ raise "Initializer requires a block to create the object" if block.nil?
25
+
26
+ @__factory.init_with = block
27
+ end
28
+
29
+ def before_create(&block)
30
+ raise "Before requires a block to call" if block.nil?
31
+
32
+ @__factory.before_create = block
33
+ end
34
+
35
+ def after_create(&block)
36
+ raise "After requires a block to call" if block.nil?
37
+
38
+ @__factory.after_create = block
39
+ end
40
+
41
+ def trait(trait, description = nil, &block)
42
+ factory = __create_factory(trait: trait, description: description, source_location: block.source_location)
43
+ factory.dsl.instance_exec(&block)
44
+ @__factory.context.add_factory(factory)
45
+ end
46
+
47
+ def singleton_method_added(name) # rubocop:disable Lint/MissingSuper
48
+ ::Kernel.raise "Adding methods (#{name}) inside factory definitions is not supported"
49
+ end
50
+
51
+ protected
52
+
53
+ def __respond_to_missing?(name:, include_all:)
54
+ name.start_with?("__") ? super : true
55
+ end
56
+
57
+ def __method_missing(name:, arguments:, keywords:, block:)
58
+ if name.start_with?("__")
59
+ super
60
+ else
61
+ unless arguments.empty?
62
+ ::Kernel.raise ::ArgumentError, <<~ERROR
63
+ Attribute '#{name}' cannot have arguments. Please use a block to configure the value
64
+ ERROR
65
+ end
66
+
67
+ ::Kernel.raise ::ArgumentError, "Attribute '#{name}' must have a block to provide the value" if block.nil?
68
+
69
+ add_attribute(name, &block)
70
+ end
71
+ end
72
+
73
+ def __default_trait_description(trait:)
74
+ trait.to_s.gsub("_", " ").chomp
75
+ end
76
+
77
+ def __create_factory(trait:, description:, source_location:)
78
+ raise NotImplementedError, "Subclasses must implement"
79
+ end
80
+
81
+ def __default_property_type(name:)
82
+ raise NotImplementedError, "Subclasses must implement"
83
+ end
84
+ end
85
+
86
+ attr_reader :parent, :description, :key, :context, :name, :source_location
87
+ attr_accessor :init_with, :before_create, :after_create
88
+
89
+ def initialize(context:, parent: nil, key:, name:, description: nil, source_location:)
90
+ raise "Parent factory must have same type" unless parent.nil? || parent.key.type == key.type
91
+
92
+ @context = context
93
+ @key = key
94
+ @name = name.to_sym
95
+ @source_location = source_location
96
+ @parent = parent
97
+ @description = description.dup.freeze
98
+ @attributes = {}
99
+ @init_with = nil
100
+ @before_create = nil
101
+ @after_create = nil
102
+ end
103
+
104
+ def create(environment:)
105
+ evaluator = AttributeEvaluator.new(key: @key, environment: environment)
106
+
107
+ collect_attributes(evaluator: evaluator)
108
+
109
+ exec_before_create(evaluator: evaluator)
110
+
111
+ object = create_object(evaluator: evaluator)
112
+
113
+ set_properties(object: object, evaluator: evaluator)
114
+
115
+ exec_after_create(evaluator: evaluator, object: object)
116
+
117
+ object
118
+ end
119
+
120
+ def attributes
121
+ @attributes.dup.freeze
122
+ end
123
+
124
+ def attribute(name:)
125
+ @attributes[name]
126
+ end
127
+
128
+ def add_attribute(attribute)
129
+ raise KeyError, "Duplicate attribute name #{attribute.name}" if @attributes.key?(attribute.name)
130
+
131
+ @attributes[attribute.name] = attribute
132
+ end
133
+
134
+ def add_overrides(overrides)
135
+ overrides.each do |name, value|
136
+ current_attribute = @parent&.lookup_attribute(name: name)
137
+
138
+ attribute = Attribute.new(name: name, type: current_attribute&.type || :property, config: value)
139
+ add_attribute(attribute)
140
+ end
141
+ end
142
+
143
+ def lookup_attribute(name:)
144
+ @attributes.fetch(name) do
145
+ @parent&.lookup_attribute(name: name)
146
+ end
147
+ end
148
+
149
+ def dsl
150
+ self.class.const_get(:Dsl).new(factory: self)
151
+ end
152
+
153
+ protected
154
+
155
+ def collect_attributes(evaluator:)
156
+ @parent&.collect_attributes(evaluator: evaluator)
157
+ evaluator.add_attributes(@attributes.values)
158
+ end
159
+
160
+ def exec_before_create(evaluator:)
161
+ @parent&.exec_before_create(evaluator: evaluator)
162
+ evaluator.evaluate(&@before_create) unless @before_create.nil?
163
+ end
164
+
165
+ def exec_after_create(evaluator:, object:)
166
+ @parent&.exec_after_create(evaluator: evaluator, object: object)
167
+ evaluator.evaluate(object, &@after_create) unless @after_create.nil?
168
+ end
169
+
170
+ def nearest_init_with
171
+ @init_with || @parent&.nearest_init_with
172
+ end
173
+
174
+ def create_object(evaluator:)
175
+ init_with = nearest_init_with
176
+
177
+ if init_with.nil?
178
+ instantiate_object(evaluator: evaluator)
179
+ else
180
+ evaluator.evaluate(&init_with)
181
+ end
182
+ end
183
+
184
+ def set_properties(object:, evaluator:)
185
+ evaluator.attributes.each_value do |attribute|
186
+ object.send(:"#{attribute.name}=", evaluator.value(name: attribute.name)) if attribute.type == :property
187
+ end
188
+ end
189
+
190
+ def instantiate_object(evaluator:)
191
+ raise "Cannot instantiate #{@key} without init function"
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calificador
4
+ module Build
5
+ # Factory for mock objects
6
+ class MockFactory < BasicFactory
7
+ # Configuration derived factories
8
+ class Dsl < BasicFactory::Dsl
9
+ def expect(&block)
10
+ __factory.expect = block
11
+ end
12
+
13
+ protected
14
+
15
+ def __create_factory(trait:, description:, source_location:)
16
+ MockFactory.new(
17
+ context: __factory.context,
18
+ parent: __factory,
19
+ key: __factory.key.with(trait),
20
+ name: [__factory.name, trait].compact.join("_"),
21
+ source_location: source_location,
22
+ description: description || __default_trait_description(trait: trait)
23
+ )
24
+ end
25
+
26
+ def __default_property_type(name:)
27
+ :transient
28
+ end
29
+ end
30
+
31
+ class ExpectProxy < Util::ProxyObject
32
+ def initialize(mock:, evaluator:)
33
+ super()
34
+
35
+ @mock = mock
36
+ @evaluator = evaluator
37
+ @environment = @evaluator.environment
38
+ @test_instance = @environment.test_instance
39
+ end
40
+
41
+ def mock
42
+ MockProxy.new(mock: @mock)
43
+ end
44
+
45
+ protected
46
+
47
+ def __respond_to_missing?(name:, include_all:)
48
+ @evaluator.attribute?(name: name) ||
49
+ !@environment.lookup_named_factory(name: name).nil? ||
50
+ @test_instance.respond_to?(name, false)
51
+ end
52
+
53
+ def __method_missing(name:, arguments:, keywords:, block:)
54
+ if @evaluator.attribute?(name: name)
55
+ @evaluator.value(name: name)
56
+ else
57
+ factory = @environment.lookup_named_factory(name: name)
58
+
59
+ if factory
60
+ @environment.create_object(key: factory.key)
61
+ else
62
+ @test_instance.send(name, *arguments, **keywords, &block)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ class MockProxy < Util::ProxyObject
69
+ def initialize(mock:)
70
+ super()
71
+
72
+ @mock = mock
73
+ end
74
+
75
+ protected
76
+
77
+ def __respond_to_missing?(name:, include_all:)
78
+ METHOD_PATTERN =~ name || super
79
+ end
80
+
81
+ def __method_missing(name:, arguments:, keywords:, block:)
82
+ if METHOD_PATTERN =~ name
83
+ MockCall.new(mock: @mock, name: name, arguments: arguments, keywords: keywords, block: block)
84
+ else
85
+ super
86
+ end
87
+ end
88
+ end
89
+
90
+ class MockCall
91
+ attr_reader :name, :arguments, :keywords, :block
92
+
93
+ def initialize(mock:, name:, arguments:, keywords:, block:)
94
+ @mock = mock
95
+ @name = name
96
+ @arguments = arguments
97
+ @keywords = keywords
98
+ @block = block
99
+ end
100
+
101
+ def >>(other)
102
+ @mock.expect(@name, other, combined_arguments)
103
+ end
104
+
105
+ protected
106
+
107
+ def combined_arguments
108
+ arguments = @arguments
109
+ arguments += [@keywords] unless keywords.empty?
110
+ arguments
111
+ end
112
+ end
113
+
114
+ attr_accessor :expect
115
+
116
+ def initialize(context:, parent: nil, key:, name:, description: nil, source_location:)
117
+ unless parent.nil?
118
+ raise "Parent factory must be a #{MockFactory}" unless parent.is_a?(MockFactory)
119
+ raise "Parent factory must have same type" unless parent.key.type == key.type
120
+ end
121
+
122
+ super(
123
+ context: context,
124
+ parent: parent,
125
+ key: key,
126
+ name: name,
127
+ description: description,
128
+ source_location: source_location
129
+ )
130
+
131
+ @expect = nil
132
+ end
133
+
134
+ def add_attribute(attribute)
135
+ raise ArgumentError, "Attribute must be transient" unless attribute.type == :transient
136
+
137
+ super
138
+ end
139
+
140
+ def set_properties(object:, evaluator:)
141
+ ExpectProxy.new(mock: object, evaluator: evaluator).instance_exec(&@expect) if @expect
142
+ end
143
+
144
+ protected
145
+
146
+ def instantiate_object(evaluator:)
147
+ ::Minitest::Mock.new
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calificador
4
+ module Build
5
+ # Factory for objects
6
+ class ObjectFactory < BasicFactory
7
+ # Configuration derived factories
8
+ class Dsl < BasicFactory::Dsl
9
+ def initialize(factory:)
10
+ super(factory: factory)
11
+
12
+ @property_type = nil
13
+ end
14
+
15
+ def transient(&block)
16
+ raise ArgumentError, "Transient requires a block" if block.nil?
17
+
18
+ old_property_type = @property_type
19
+ @property_type = :transient
20
+
21
+ begin
22
+ instance_exec(&block)
23
+ ensure
24
+ @property_type = old_property_type
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def __create_factory(trait:, description:, source_location:)
31
+ ObjectFactory.new(
32
+ context: __factory.context,
33
+ parent: __factory,
34
+ key: __factory.key.with(trait),
35
+ name: [__factory.name, trait].compact.join("_"),
36
+ source_location: source_location,
37
+ description: description || __default_trait_description(trait: trait)
38
+ )
39
+ end
40
+
41
+ def __default_property_type(name:)
42
+ __factory.parent&.attribute(name: name)&.type || @property_type || :property
43
+ end
44
+ end
45
+
46
+ def initialize(context:, parent: nil, key:, name:, description: nil, source_location:)
47
+ unless parent.nil?
48
+ raise "Parent factory must be a #{ObjectFactory}" unless parent.is_a?(ObjectFactory)
49
+ raise "Parent factory must have same type" unless parent.key.type == key.type
50
+ end
51
+
52
+ super(
53
+ context: context,
54
+ parent: parent,
55
+ key: key,
56
+ name: name,
57
+ description: description,
58
+ source_location: source_location
59
+ )
60
+ end
61
+
62
+ protected
63
+
64
+ def instantiate_object(evaluator:)
65
+ parameters = []
66
+ options = {}
67
+
68
+ @key.type.instance_method(:initialize).parameters.each do |type, name|
69
+ case type
70
+ when :req
71
+ parameters << evaluator.value(name: name)
72
+ when :opt
73
+ parameters << evaluator.value(name: name) if evaluator.attribute?(name: name)
74
+ when :keyreq
75
+ options[name] = evaluator.value(name: name)
76
+ when :key
77
+ options[name] = evaluator.value(name: name) if evaluator.attribute?(name: name)
78
+ end
79
+ end
80
+
81
+ @key.type.new(*parameters, **options)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Calificador
4
- module Build
5
- # Trait description
6
- class Trait < AttributeContainer
7
- # Configuration proxy for traits
8
- class Dsl < AttributeContainer::Dsl
9
- end
10
-
11
- attr_reader :name
12
-
13
- def initialize(parent:, name:, description: nil)
14
- super(parent: parent, description: description)
15
-
16
- @name = name.to_sym
17
- end
18
- end
19
- end
20
- end