calificador 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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