cloud-templates 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +29 -0
  3. data/.simplecov +6 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +201 -0
  6. data/NOTICE +13 -0
  7. data/README.md +124 -0
  8. data/Rakefile +27 -0
  9. data/cloud-templates.gemspec +27 -0
  10. data/examples/lib/user_directory/artifacts/catalogized.rb +11 -0
  11. data/examples/lib/user_directory/artifacts/group.rb +37 -0
  12. data/examples/lib/user_directory/artifacts/ided.rb +11 -0
  13. data/examples/lib/user_directory/artifacts/organization.rb +17 -0
  14. data/examples/lib/user_directory/artifacts/pathed.rb +22 -0
  15. data/examples/lib/user_directory/artifacts/person.rb +20 -0
  16. data/examples/lib/user_directory/artifacts/team.rb +31 -0
  17. data/examples/lib/user_directory/artifacts/unit.rb +24 -0
  18. data/examples/lib/user_directory/artifacts/user.rb +29 -0
  19. data/examples/lib/user_directory/render/etc/artifact_view.rb +15 -0
  20. data/examples/lib/user_directory/render/etc/composite_view.rb +26 -0
  21. data/examples/lib/user_directory/render/etc/group_view.rb +23 -0
  22. data/examples/lib/user_directory/render/etc/person_view.rb +19 -0
  23. data/examples/lib/user_directory/render/etc/registry.rb +33 -0
  24. data/examples/lib/user_directory/render/etc/user_view.rb +35 -0
  25. data/examples/lib/user_directory/render/etc.rb +3 -0
  26. data/examples/lib/user_directory/render/ldap/artifact_view.rb +27 -0
  27. data/examples/lib/user_directory/render/ldap/composite_view.rb +32 -0
  28. data/examples/lib/user_directory/render/ldap/group_view.rb +28 -0
  29. data/examples/lib/user_directory/render/ldap/organization_view.rb +26 -0
  30. data/examples/lib/user_directory/render/ldap/person_view.rb +39 -0
  31. data/examples/lib/user_directory/render/ldap/registry.rb +16 -0
  32. data/examples/lib/user_directory/render/ldap/unit_view.rb +26 -0
  33. data/examples/lib/user_directory/render/ldap/user_view.rb +39 -0
  34. data/examples/lib/user_directory/render/ldap.rb +3 -0
  35. data/examples/lib/user_directory/utils.rb +18 -0
  36. data/examples/lib/user_directory.rb +23 -0
  37. data/examples/lib_path.rb +2 -0
  38. data/examples/spec/spec_helper.rb +1 -0
  39. data/examples/spec/user_directory_spec.rb +568 -0
  40. data/lib/aws/templates/artifact.rb +140 -0
  41. data/lib/aws/templates/composite.rb +178 -0
  42. data/lib/aws/templates/exceptions.rb +221 -0
  43. data/lib/aws/templates/render/registry.rb +60 -0
  44. data/lib/aws/templates/render/utils/base_type_views.rb +131 -0
  45. data/lib/aws/templates/render/view.rb +127 -0
  46. data/lib/aws/templates/render.rb +72 -0
  47. data/lib/aws/templates/utils/artifact_storage.rb +141 -0
  48. data/lib/aws/templates/utils/contextualized/filters.rb +437 -0
  49. data/lib/aws/templates/utils/contextualized/hash.rb +13 -0
  50. data/lib/aws/templates/utils/contextualized/nil.rb +13 -0
  51. data/lib/aws/templates/utils/contextualized/proc.rb +13 -0
  52. data/lib/aws/templates/utils/contextualized.rb +113 -0
  53. data/lib/aws/templates/utils/default.rb +185 -0
  54. data/lib/aws/templates/utils/dependency/enumerable.rb +13 -0
  55. data/lib/aws/templates/utils/dependency/object.rb +46 -0
  56. data/lib/aws/templates/utils/dependency.rb +121 -0
  57. data/lib/aws/templates/utils/dependent.rb +28 -0
  58. data/lib/aws/templates/utils/inheritable.rb +52 -0
  59. data/lib/aws/templates/utils/late_bound.rb +89 -0
  60. data/lib/aws/templates/utils/memoized.rb +27 -0
  61. data/lib/aws/templates/utils/named.rb +19 -0
  62. data/lib/aws/templates/utils/options.rb +279 -0
  63. data/lib/aws/templates/utils/parametrized/constraints.rb +423 -0
  64. data/lib/aws/templates/utils/parametrized/getters.rb +293 -0
  65. data/lib/aws/templates/utils/parametrized/guarded.rb +32 -0
  66. data/lib/aws/templates/utils/parametrized/mapper.rb +73 -0
  67. data/lib/aws/templates/utils/parametrized/nested.rb +72 -0
  68. data/lib/aws/templates/utils/parametrized/transformations.rb +660 -0
  69. data/lib/aws/templates/utils/parametrized.rb +240 -0
  70. data/lib/aws/templates/utils.rb +219 -0
  71. data/lib/aws/templates.rb +16 -0
  72. data/spec/aws/templates/artifact_spec.rb +161 -0
  73. data/spec/aws/templates/composite_spec.rb +361 -0
  74. data/spec/aws/templates/render/utils/base_type_views_spec.rb +104 -0
  75. data/spec/aws/templates/render_spec.rb +62 -0
  76. data/spec/aws/templates/utils/as_named_spec.rb +31 -0
  77. data/spec/aws/templates/utils/contextualized/filters_spec.rb +108 -0
  78. data/spec/aws/templates/utils/contextualized_spec.rb +115 -0
  79. data/spec/aws/templates/utils/late_bound_spec.rb +52 -0
  80. data/spec/aws/templates/utils/options_spec.rb +67 -0
  81. data/spec/aws/templates/utils/parametrized/constraint_spec.rb +175 -0
  82. data/spec/aws/templates/utils/parametrized/getters_spec.rb +139 -0
  83. data/spec/aws/templates/utils/parametrized/transformation_spec.rb +314 -0
  84. data/spec/aws/templates/utils/parametrized_spec.rb +241 -0
  85. data/spec/spec_helper.rb +6 -0
  86. metadata +244 -0
@@ -0,0 +1,240 @@
1
+ require 'aws/templates/exceptions'
2
+ require 'aws/templates/utils/parametrized/guarded'
3
+ require 'aws/templates/utils/inheritable'
4
+ require 'aws/templates/utils/dependent'
5
+ require 'set'
6
+
7
+ module Aws
8
+ module Templates
9
+ module Utils
10
+ ##
11
+ # 'parameter' DSL to specify artifact parameters checks
12
+ #
13
+ # The module provides the basic class-level methods to define
14
+ # so-called parameters. A parameter is a reader-like method of
15
+ # value extraction, constraints checking and transformation. Essentially,
16
+ # it's domain-specific extended implementation of attr_reader.
17
+ module Parametrized
18
+ include Guarded
19
+ include Inheritable
20
+ include Dependent
21
+
22
+ ##
23
+ # Parameter object
24
+ #
25
+ # The object incorporates parameter specification and basic logic for
26
+ # value extraction, checking and transformation. Parameter objects
27
+ # are created at each parameter description.
28
+ class Parameter
29
+ attr_reader :name
30
+ attr_accessor :description
31
+
32
+ def getter(instance = nil)
33
+ @getter || (
34
+ instance &&
35
+ (
36
+ (instance.respond_to?(:getter) && instance.getter) ||
37
+ (instance.class.respond_to?(:getter) && instance.class.getter)
38
+ )
39
+ )
40
+ end
41
+
42
+ attr_accessor :transform
43
+ attr_accessor :constraint
44
+ attr_accessor :klass
45
+
46
+ ##
47
+ # Create a parameter object with given specification
48
+ #
49
+ # * +name+ - parameter name
50
+ # * +enclosing_class+ - the class the parameter was declared at
51
+ # * +specification+ - parameter specification; it includes:
52
+ # ** +:description+ - human-readable parameter description
53
+ # ** +:getter+ - getter Proc which will be used for parameter extraction
54
+ # from input hash or plain value. The Proc shouldn't
55
+ # expect any arguments and it will be executed in
56
+ # the instance context. If a plain value is passed
57
+ # it will be used as is. If the argument is not specified
58
+ # then value will be extracted from the input hash by
59
+ # the parameter name (see Getter for more information)
60
+ # ** +:transform+ - transform Proc which will be used for transforming
61
+ # extracted value. It should expect single parameter
62
+ # and it will be executed in instance context.
63
+ # if not specified not transformation will be
64
+ # performed (see Transformation for more information)
65
+ # ** +:constraint+ - constraint Proc which will be used to check
66
+ # the value after transformation step. The Proc
67
+ # is expected to receive one arg and throw an
68
+ # exception if constraints are not met
69
+ # (see Constraint for more information)
70
+ def initialize(name, enclosing_class, specification = {})
71
+ @name = name
72
+ set_specification(enclosing_class, specification)
73
+ end
74
+
75
+ ##
76
+ # Get the parameter value from the instance
77
+ #
78
+ # It is used internally in auto-generated accessors to get the value
79
+ # from input hash. The method extracts value from the hash and
80
+ # pushes it through transformation and constraint stages. Also,
81
+ # you can specify value as the optional parameter so getter even
82
+ # if present will be ignored. It relies on presence of options
83
+ # accessor in the instance.
84
+ # * +instance+ - instance to extract the parameter value from
85
+ def get(instance)
86
+ process_value(instance, extract_value(instance))
87
+ end
88
+
89
+ def process_value(instance, value)
90
+ value = instance.instance_exec(self, value, &transform) if transform
91
+ instance.instance_exec(self, value, &constraint) if constraint
92
+ value
93
+ end
94
+
95
+ private
96
+
97
+ def extract_value(instance)
98
+ raise ParameterGetterIsNotDefined.new(self) unless getter(instance)
99
+ execute_getter(instance, getter(instance))
100
+ end
101
+
102
+ def execute_getter(instance, getter)
103
+ if getter.respond_to?(:to_hash)
104
+ getter
105
+ elsif getter.respond_to?(:to_proc)
106
+ instance.instance_exec(self, &getter)
107
+ else
108
+ getter
109
+ end
110
+ end
111
+
112
+ ALLOWED_SPECIFICATION_ENTRIES = Set.new %i[description getter transform constraint]
113
+
114
+ def set_specification(enclosing_class, specification) # :nodoc:
115
+ @klass = enclosing_class
116
+
117
+ wrong_options = specification.keys.reject do |option_name|
118
+ ALLOWED_SPECIFICATION_ENTRIES.include?(option_name)
119
+ end
120
+
121
+ raise_wrong_options(wrong_options) unless wrong_options.empty?
122
+
123
+ process_specification(specification)
124
+ end
125
+
126
+ def raise_wrong_options(wrong_options)
127
+ raise ParameterSpecificationIsInvalid.new(self, wrong_options)
128
+ end
129
+
130
+ def process_specification(spec)
131
+ @description = spec[:description] if spec.key?(:description)
132
+ @getter = spec[:getter] if spec.key?(:getter)
133
+ @transform = spec[:transform] if spec.key?(:transform)
134
+ @constraint = spec[:constraint] if spec.key?(:constraint)
135
+ end
136
+ end
137
+
138
+ instance_scope do
139
+ ##
140
+ # Lazy initializer
141
+ def dependencies
142
+ if @dependencies.nil?
143
+ @dependencies = Set.new
144
+ depends_on(parameter_names.map { |name| send(name) })
145
+ end
146
+
147
+ @dependencies
148
+ end
149
+
150
+ ##
151
+ # Parameter names list
152
+ #
153
+ # Instance-level alias for list_all_parameter_names
154
+ def parameter_names
155
+ self.class.list_all_parameter_names
156
+ end
157
+
158
+ ##
159
+ # Validate all parameters
160
+ #
161
+ # Performs calculation of all specified parameters to check options validity
162
+ def validate
163
+ parameter_names.each { |name| send(name) }
164
+ end
165
+
166
+ attr_reader :getter
167
+ end
168
+
169
+ ##
170
+ # Class-level mixins
171
+ #
172
+ # It's a DSL extension to declaratively define parameters.
173
+ class_scope do
174
+ ##
175
+ # List all defined parameter names
176
+ #
177
+ # The list includes both the class parameters and all ancestor
178
+ # parameters.
179
+ def list_all_parameter_names
180
+ ancestors
181
+ .select { |mod| mod.include?(Parametrized) }
182
+ .inject(Set.new) do |parameter_collection, mod|
183
+ parameter_collection.merge(mod.parameters.keys)
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Parameters defined in the class
189
+ #
190
+ # Returns map from parameter name to parameter object of the parameters
191
+ # defined only in the class.
192
+ def parameters
193
+ @parameters ||= {}
194
+ end
195
+
196
+ ##
197
+ # Get parameter object by name
198
+ #
199
+ # Look-up by the parameter object name recursively through class
200
+ # inheritance hierarchy.
201
+ # * +parameter_alias+ - parameter name
202
+ def get_parameter(parameter_alias)
203
+ ancestor =
204
+ ancestors
205
+ .select { |mod| mod.include?(Parametrized) }
206
+ .find { |mod| mod.parameters.key?(parameter_alias) }
207
+
208
+ ancestor.parameters[parameter_alias] if ancestor
209
+ end
210
+
211
+ ##
212
+ # Class-level parameter declaration method
213
+ #
214
+ # Being a part of parameter declaration DSL, it constructs
215
+ # parameter object, stores it in parameters class level registry
216
+ # and creates a reader method for it. It will throw exception
217
+ # if the parameter name is already occupied by a method or a parameter
218
+ # with the name already exists in the class or any ancestor
219
+ # * +parameter_alias+ - parameter name
220
+ # * +specification+ - parameter specification hash
221
+ def parameter(name, spec = {})
222
+ raise_already_exists(name) if method_defined?(name)
223
+
224
+ parameter_object = Parameter.new(name, self, spec)
225
+ parameters[name] = parameter_object
226
+ define_method(name) { guarded_get(self, parameter_object) }
227
+ end
228
+
229
+ def raise_already_exists(name)
230
+ parameter_object = get_parameter(name)
231
+
232
+ raise(ParameterAlreadyExist.new(parameter_object)) if parameter_object
233
+
234
+ raise ParameterMethodNameConflict.new(instance_method(name))
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,219 @@
1
+ require 'aws/templates/exceptions'
2
+
3
+ module Aws
4
+ module Templates
5
+ ##
6
+ # Variable utility functions used through the code
7
+ module Utils
8
+ RECURSIVE_METHODS = %i[keys [] include?].freeze
9
+
10
+ ##
11
+ # If the object is "recursive"
12
+ #
13
+ # Checks if object satisfies "recursive" concept. See RECURSIVE_METHODS for the list
14
+ # of methods
15
+ def self.recursive?(obj)
16
+ RECURSIVE_METHODS.all? { |m| obj.respond_to?(m) }
17
+ end
18
+
19
+ ##
20
+ # If the object is "scalar"
21
+ #
22
+ # Checks if object satisfies "scalar" concept.
23
+ def self.scalar?(obj)
24
+ !obj.respond_to?(:[])
25
+ end
26
+
27
+ ##
28
+ # If object is hashable
29
+ #
30
+ # Checks if object can be transformed into Hash
31
+ def self.hashable?(obj)
32
+ obj.respond_to?(:to_hash)
33
+ end
34
+
35
+ PARAMETRIZED_METHODS = [:parameter_names].freeze
36
+
37
+ ##
38
+ # If the object is "parametrized"
39
+ #
40
+ # Checks if object satisfies "parametrized" concept. See PARAMETRIZED_METHODS for the list
41
+ # of methods
42
+ def self.parametrized?(obj)
43
+ PARAMETRIZED_METHODS.all? { |m| obj.respond_to?(m) }
44
+ end
45
+
46
+ ##
47
+ # If object is a list
48
+ #
49
+ # Checks if object can be transformed into Array
50
+ def self.list?(obj)
51
+ obj.respond_to?(:to_ary)
52
+ end
53
+
54
+ ##
55
+ # Duplicate hash recursively
56
+ #
57
+ # Duplicate the hash and all nested hashes recursively
58
+ def self.deep_dup(original)
59
+ return original unless Utils.hashable?(original)
60
+
61
+ duplicate = original.dup.to_hash
62
+ duplicate.each_pair { |k, v| duplicate[k] = deep_dup(v) }
63
+
64
+ duplicate
65
+ end
66
+
67
+ ##
68
+ # Merges two nested hashes
69
+ #
70
+ # The core element of the whole framework. The principle is simple:
71
+ # both arguments are transformed to hashes if they support :to_hash
72
+ # method, the resulting hashes are merged with the standard method
73
+ # with creating a new hash. Second element takes preference if the
74
+ # function reached bottom level of recursion with only scalars left.
75
+ #
76
+ # Raises ArgumentError if a and b have incompatible types hence
77
+ # they can't be merged
78
+ #
79
+ # ==== Example
80
+ #
81
+ # Options.merge('a', 'b') # => 'b'
82
+ # Options.merge({'1'=>'2'}, {'3'=>'4'}) # => {'1'=>'2', '3'=>'4'}
83
+ # Options.merge(
84
+ # { '1' => { '2' => '3' } },
85
+ # { '1' => { '4' => '5' } }
86
+ # ) # => { '1' => { '2' => '3', '4'=>'5' } }
87
+ ##
88
+ # Recursively merge two "recursive" objects
89
+ # PS: Yes I know that there is "merge" method for *hashes*.
90
+ def self.merge(a, b)
91
+ return hashify(b) unless Utils.recursive?(a) && Utils.recursive?(b)
92
+ _merge_back(_merge_forward(a, b), b)
93
+ end
94
+
95
+ def self._merge_forward(a, b)
96
+ a.keys.each_with_object({}) do |k, hsh|
97
+ hsh[k] = b[k].nil? ? hashify(a[k]) : merge(a[k], b[k])
98
+ end
99
+ end
100
+
101
+ def self._merge_back(result, b)
102
+ b.keys.reject { |k| result.include?(k) }.each_with_object(result) { |k, res| res[k] = b[k] }
103
+ end
104
+
105
+ def self.hashify(v)
106
+ return v unless Utils.recursive?(v)
107
+ v.keys.each_with_object({}) { |k, hsh| hsh[k] = hashify(v[k]) }
108
+ end
109
+
110
+ ##
111
+ # Deletion marker
112
+ #
113
+ # Since Options use lazy merging (effectively keeping all hashes passed during
114
+ # initialization immutable) and iterates through all of them during value look-up, we need
115
+ # a way of marking some branch as deleted when deletion operation is invoked on an Options
116
+ # object. So, the algorithm marks branch with the object to stop iteration during look-up.
117
+ DELETED_MARKER = Object.new
118
+
119
+ ##
120
+ # Get a parameter from resulting hash or any nested part of it
121
+ #
122
+ # The method can access resulting hash as a tree performing
123
+ # traverse as needed. Also, it handles nil-pointer situations
124
+ # correctly so you will get no exception but just 'nil' even when
125
+ # the whole branch you're trying to access don't exist or contains
126
+ # non-hash value somewhere in the middle. Also, the method
127
+ # recognizes asterisk (*) hash records which is an analog of
128
+ # match-all or default values for some sub-branch.
129
+ #
130
+ # * +path+ - an array representing path of the value in the nested
131
+ # hash
132
+ #
133
+ # ==== Example
134
+ #
135
+ # opts = Options.new(
136
+ # 'a' => {
137
+ # 'b' => 'c',
138
+ # '*' => { '*' => 2 }
139
+ # },
140
+ # 'd' => 1
141
+ # )
142
+ # opts.to_hash # => { 'a' => { 'b' => 'c', '*' => { '*' => 2 } }, 'd' => 1 }
143
+ # opts['a'] # => Options.new('b' => 'c', '*' => { '*' => 2 })
144
+ # opts['a', 'b'] # => 'c'
145
+ # opts['d', 'e'] # => nil
146
+ # # multi-level wildcard match
147
+ # opts['a', 'z', 'r'] # => 2
148
+ def self.lookup(value, path)
149
+ # we stop lookup and return nil if nil is encountered
150
+ return if value.nil?
151
+ # value was deleted in this layer
152
+ raise OptionValueDeleted.new(path) if value == DELETED_MARKER
153
+ # we reached our target! returning it
154
+ return value if path.nil? || path.empty?
155
+ # we still have some part of path to traverse but scalar was found
156
+ raise OptionScalarOnTheWay.new(value, path) if Utils.scalar?(value)
157
+
158
+ _lookup_recursively(value, path.dup)
159
+ end
160
+
161
+ def self._lookup_recursively(value, path)
162
+ current_key = path.shift
163
+
164
+ # is there a value defined for the key in the current recursive structure?
165
+ if value.include?(current_key)
166
+ # yes - look-up the rest of the path
167
+ return_value = lookup(value[current_key], path)
168
+ # if value was still not found - resorting to wildcard path
169
+ return_value.nil? ? lookup(value[:*], path) : return_value
170
+ elsif value.include?(:*)
171
+ # if there is no key but the recursive has a default value defined - dive into the
172
+ # wildcard branch
173
+ lookup(value[:*], path)
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Sets a value in hierarchy
179
+ #
180
+ # Sets a path in a nested recursive hash structure to the specified value
181
+ #
182
+ # * +container+ - container with the specified path
183
+ # * +value+ - the value to set the path to
184
+ # * +path+ - path containing the target value
185
+ def self.set_recursively(container, value, path)
186
+ last_key = path.pop
187
+
188
+ last_branch = path.inject(container) do |obj, current_key|
189
+ raise OptionScalarOnTheWay.new(obj, path) unless Utils.recursive?(obj)
190
+ if obj.include?(current_key)
191
+ obj[current_key]
192
+ else
193
+ obj[current_key] = {}
194
+ end
195
+ end
196
+
197
+ last_branch[last_key] = value
198
+ end
199
+
200
+ ##
201
+ # Select object recursively
202
+ #
203
+ # Selects objects recursively from the specified container with specified block predicate.
204
+ #
205
+ # * +container+ - container to recursively select items from
206
+ # * +blk+ - the predicate
207
+ def self.select_recursively(container, &blk)
208
+ container.keys.each_with_object([]) do |k, collection|
209
+ value = container[k]
210
+ if blk.call(value)
211
+ collection << value
212
+ elsif Utils.recursive?(value)
213
+ collection.concat(select_recursively(value, &blk))
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,16 @@
1
+ require 'aws/templates/artifact'
2
+ require 'aws/templates/composite'
3
+ require 'aws/templates/render'
4
+ require 'aws/templates/utils/named'
5
+ require 'aws/templates/utils/parametrized/constraints'
6
+ require 'aws/templates/utils/parametrized/transformations'
7
+ require 'aws/templates/utils/parametrized/getters'
8
+
9
+ ##
10
+ # :main: README.md
11
+ module Aws
12
+ ##
13
+ # Main namespace for the library-related functionality
14
+ module Templates
15
+ end
16
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+ require 'aws/templates/artifact'
3
+
4
+ module TestTest
5
+ module A; end
6
+ module B; end
7
+ end
8
+
9
+ describe Aws::Templates::Artifact do
10
+ let(:artifact_class) do
11
+ Class.new(Aws::Templates::Artifact) do
12
+ default a: 'b',
13
+ b: proc { options[:c].upcase }
14
+
15
+ parameter :c
16
+ parameter :d
17
+ end
18
+ end
19
+
20
+ let(:dependency) do
21
+ Struct.new(:"dependency?", :dependencies)
22
+ end
23
+
24
+ let(:just_object) do
25
+ Struct.new(:root)
26
+ end
27
+
28
+ context 'featuring class is created' do
29
+ let(:featuring_class) { artifact_class.featuring(TestTest::A, TestTest::B) }
30
+
31
+ it 'returns correct class name' do
32
+ expect(featuring_class.to_s).to match(/Subclass.*TestTest..B.*TestTest..A/)
33
+ end
34
+ end
35
+
36
+ context 'instance created' do
37
+ let(:params) do
38
+ {
39
+ c: 'qwe',
40
+ d: {
41
+ e: {
42
+ f: { a: dependency.new(true, [just_object.new(2)]) },
43
+ g: { d: dependency.new(true, [just_object.new(1)]) }
44
+ }
45
+ }
46
+ }
47
+ end
48
+
49
+ let(:instance) { artifact_class.new(params) }
50
+
51
+ context 'label is not specified' do
52
+ it 'contains auto-generated label' do
53
+ expect(instance.label).not_to be_nil
54
+ end
55
+ end
56
+
57
+ context 'root is not specified' do
58
+ it 'root is not empty' do
59
+ expect(instance.root).not_to be_nil
60
+ end
61
+ it 'doesn\'t have any dependencies' do
62
+ expect(instance.dependencies).to be_empty
63
+ end
64
+ end
65
+ context 'label is specified' do
66
+ before { params[:label] = 'b' }
67
+ it 'contains passed label' do
68
+ expect(instance.label).to be == 'b'
69
+ end
70
+ end
71
+ context 'root is specified' do
72
+ before { params[:root] = 1 }
73
+ it 'contains one dependency' do
74
+ expect(instance.dependencies).to be == [just_object.new(1)].to_set
75
+ end
76
+ it 'contains passed root' do
77
+ expect(instance.root).to be == 1
78
+ end
79
+ end
80
+ context 'different root is specified' do
81
+ before { params[:root] = 2 }
82
+ it 'contains one dependency' do
83
+ expect(instance.dependencies).to be == [just_object.new(2)].to_set
84
+ end
85
+ end
86
+ context 'no overrides specified' do
87
+ before { params.merge!(root: 3, label: 'thing') }
88
+
89
+ let(:expected) do
90
+ {
91
+ label: 'thing',
92
+ a: 'b',
93
+ b: 'QWE',
94
+ c: 'qwe',
95
+ d: {
96
+ e: {
97
+ f: { a: dependency.new(true, [just_object.new(2)]) },
98
+ g: { d: dependency.new(true, [just_object.new(1)]) }
99
+ }
100
+ },
101
+ root: 3
102
+ }
103
+ end
104
+
105
+ it 'calculates with defaults' do
106
+ expect(instance.options.to_hash).to be == expected
107
+ end
108
+ end
109
+ context 'override is present in input hash' do
110
+ before { params.merge!(root: 3, label: 'thing', a: 'rty') }
111
+
112
+ let(:expected) do
113
+ {
114
+ label: 'thing',
115
+ a: 'rty',
116
+ b: 'QWE',
117
+ c: 'qwe',
118
+ d: {
119
+ e: {
120
+ f: { a: dependency.new(true, [just_object.new(2)]) },
121
+ g: { d: dependency.new(true, [just_object.new(1)]) }
122
+ }
123
+ },
124
+ root: 3
125
+ }
126
+ end
127
+
128
+ it 'calculates with defaults' do
129
+ expect(instance.options.to_hash).to be == expected
130
+ end
131
+ end
132
+ end
133
+
134
+ context 'instance of child class created' do
135
+ let(:child_class) do
136
+ Class.new(artifact_class) do
137
+ default a: 'c',
138
+ c: proc { options[:d].tr(' ', '.') }
139
+ end
140
+ end
141
+
142
+ let(:params) { { root: 3, label: 'thing', d: 'q w e' } }
143
+
144
+ let(:child_instance) { child_class.new(params) }
145
+
146
+ let(:expected) do
147
+ {
148
+ root: 3,
149
+ label: 'thing',
150
+ a: 'c',
151
+ b: 'Q.W.E',
152
+ c: 'q.w.e',
153
+ d: 'q w e'
154
+ }
155
+ end
156
+
157
+ it 'overlays overrides' do
158
+ expect(child_instance.options.to_hash).to be == expected
159
+ end
160
+ end
161
+ end