cloud-templates 0.1.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 (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