test_dummy 0.4.0 → 0.5.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.
- checksums.yaml +7 -0
- data/Gemfile +4 -2
- data/README.md +3 -1
- data/Rakefile +7 -4
- data/VERSION +1 -1
- data/lib/test_dummy.rb +55 -306
- data/lib/test_dummy/definition.rb +113 -0
- data/lib/test_dummy/loader.rb +41 -0
- data/lib/test_dummy/operation.rb +364 -0
- data/lib/test_dummy/railtie.rb +15 -6
- data/lib/test_dummy/support.rb +1 -1
- data/lib/test_dummy/test_helper.rb +16 -5
- data/test/db/migrate/0002_create_accounts.rb +3 -0
- data/test/db/migrate/0005_create_users.rb +12 -0
- data/test/dummy/account.rb +9 -1
- data/test/dummy/bill.rb +6 -0
- data/test/dummy/broken.rb +1 -0
- data/test/dummy/user.rb +8 -0
- data/test/dummy/user/admin.rb +5 -0
- data/test/helper.rb +26 -7
- data/test/models/broken.rb +3 -0
- data/test/models/user.rb +45 -0
- data/test/models/user/admin.rb +22 -0
- data/test/unit/test_account.rb +78 -2
- data/test/unit/test_bill.rb +28 -4
- data/test/unit/test_definition.rb +59 -0
- data/test/unit/test_item.rb +13 -2
- data/test/unit/test_loader.rb +23 -0
- data/test/unit/test_operation.rb +570 -0
- data/test/unit/test_support.rb +10 -0
- data/test/unit/test_test_dummy.rb +8 -2
- data/test/unit/test_test_helper.rb +22 -0
- data/test/unit/test_user.rb +18 -0
- data/test_dummy.gemspec +29 -7
- metadata +58 -22
@@ -0,0 +1,113 @@
|
|
1
|
+
class TestDummy::Definition
|
2
|
+
# == Extensions ===========================================================
|
3
|
+
|
4
|
+
# == Properties ===========================================================
|
5
|
+
|
6
|
+
attr_reader :operations
|
7
|
+
|
8
|
+
# == Class Methods ========================================================
|
9
|
+
|
10
|
+
# == Instance Methods =====================================================
|
11
|
+
|
12
|
+
def initialize(operations = nil)
|
13
|
+
@operations = operations ? operations.dup : [ ]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Applies the operations defined in this definition to the model supplied,
|
17
|
+
# taking into account any options used for creation and only triggering based
|
18
|
+
# on the tags specified.
|
19
|
+
def apply!(model, create_options, tags)
|
20
|
+
@operations.each do |operation|
|
21
|
+
next if (operation.after)
|
22
|
+
|
23
|
+
operation.apply!(model, create_options, tags)
|
24
|
+
end
|
25
|
+
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply_after_save!(model, create_options, tags)
|
30
|
+
@operations.each do |operation|
|
31
|
+
next unless (operation.after == :save)
|
32
|
+
|
33
|
+
operation.apply!(model, create_options, tags)
|
34
|
+
end
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a copy of this Definition.
|
40
|
+
def clone
|
41
|
+
self.class.new(@operations)
|
42
|
+
end
|
43
|
+
alias_method :dup, :clone
|
44
|
+
|
45
|
+
# Returns a list of fields that could be populated with dummy data when the
|
46
|
+
# given tags are employed.
|
47
|
+
def fields(*tags)
|
48
|
+
tags = tags.flatten.compact
|
49
|
+
|
50
|
+
@operations.each_with_object([ ]) do |operation, collection|
|
51
|
+
if (_fields = operation.fields(tags))
|
52
|
+
collection.concat(_fields)
|
53
|
+
end
|
54
|
+
end.compact.uniq
|
55
|
+
end
|
56
|
+
|
57
|
+
def fields?(*matching_fields)
|
58
|
+
matching_fields = Hash[
|
59
|
+
matching_fields.flatten.compact.collect do |field|
|
60
|
+
[ field.to_sym, false ]
|
61
|
+
end
|
62
|
+
]
|
63
|
+
|
64
|
+
@operations.each do |operation|
|
65
|
+
operation_fields = operation.fields
|
66
|
+
|
67
|
+
next unless (operation_fields)
|
68
|
+
|
69
|
+
operation_fields.each do |field|
|
70
|
+
next unless (field)
|
71
|
+
|
72
|
+
matching_fields[field] = true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
!matching_fields.find do |field, found|
|
77
|
+
!found
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def [](field)
|
82
|
+
field = field.to_sym
|
83
|
+
|
84
|
+
@operations.select do |operation|
|
85
|
+
operation.fields.include?(field)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def define_operation(model_class, fields, options)
|
90
|
+
if (fields.any?)
|
91
|
+
fields.each do |field|
|
92
|
+
field_options = options.merge(
|
93
|
+
:fields => [ field ].flatten.collect(&:to_sym)
|
94
|
+
)
|
95
|
+
|
96
|
+
model_class, foreign_key = TestDummy::Support.reflection_properties(model_class, field)
|
97
|
+
|
98
|
+
if (model_class and foreign_key)
|
99
|
+
field_options[:model_class] ||= model_class
|
100
|
+
field_options[:foreign_key] ||= foreign_key
|
101
|
+
end
|
102
|
+
|
103
|
+
@operations << TestDummy::Operation.new(field_options)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
@operations << TestDummy::Operation.new(options)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def <<(operation)
|
111
|
+
@operations << operation
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class TestDummy::Loader
|
2
|
+
# == Class Methods ========================================================
|
3
|
+
|
4
|
+
def self.load!(model_class)
|
5
|
+
@instance ||= new
|
6
|
+
|
7
|
+
@instance[model_class.to_s]
|
8
|
+
end
|
9
|
+
|
10
|
+
# == Instance Methods =====================================================
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@loaded = { }
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](class_name)
|
17
|
+
return @loaded[class_name] if (@loaded.key?(class_name))
|
18
|
+
|
19
|
+
@loaded[class_name] = nil
|
20
|
+
|
21
|
+
dummy_path = File.expand_path(
|
22
|
+
"#{class_name.to_s.underscore}.rb",
|
23
|
+
TestDummy.dummy_extensions_path
|
24
|
+
)
|
25
|
+
|
26
|
+
if (File.exist?(dummy_path))
|
27
|
+
begin
|
28
|
+
Kernel.load(dummy_path)
|
29
|
+
|
30
|
+
@loaded[class_name] = true
|
31
|
+
rescue LoadError => e
|
32
|
+
@loaded[class_name] = e
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@loaded[class_name] = false
|
36
|
+
end
|
37
|
+
rescue LoadError => e
|
38
|
+
# Persist that this load attempt failed and don't retry later.
|
39
|
+
@loaded[class_name] = e
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,364 @@
|
|
1
|
+
class TestDummy::Operation
|
2
|
+
# == Constants ============================================================
|
3
|
+
|
4
|
+
VALID_OPTIONS = [
|
5
|
+
:block,
|
6
|
+
:fields,
|
7
|
+
:only,
|
8
|
+
:except,
|
9
|
+
:after,
|
10
|
+
:force,
|
11
|
+
:with,
|
12
|
+
:from,
|
13
|
+
:model_class,
|
14
|
+
:foreign_key,
|
15
|
+
:inherit
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
# == Properties ===========================================================
|
19
|
+
|
20
|
+
attr_reader :source_methods
|
21
|
+
attr_reader :source_keys
|
22
|
+
attr_reader :only
|
23
|
+
attr_reader :except
|
24
|
+
attr_reader :after
|
25
|
+
attr_reader :model_class
|
26
|
+
attr_reader :foreign_key
|
27
|
+
|
28
|
+
# == Instance Methods =====================================================
|
29
|
+
|
30
|
+
def initialize(options)
|
31
|
+
@blocks = [ ]
|
32
|
+
|
33
|
+
invalid_options = options.keys - VALID_OPTIONS
|
34
|
+
|
35
|
+
if (invalid_options.any?)
|
36
|
+
raise TestDummy::Exception, "Unknown options to #{self.class}: #{invalid_options.inspect}"
|
37
|
+
end
|
38
|
+
|
39
|
+
assign_block_options!(options)
|
40
|
+
assign_fields_options!(options)
|
41
|
+
|
42
|
+
assign_only_options!(options)
|
43
|
+
assign_except_options!(options)
|
44
|
+
assign_after_options!(options)
|
45
|
+
|
46
|
+
assign_force_options!(options)
|
47
|
+
|
48
|
+
assign_with_options!(options)
|
49
|
+
|
50
|
+
assign_from_options!(options)
|
51
|
+
assign_reflection_options!(options)
|
52
|
+
assign_inherit_options!(options)
|
53
|
+
assign_reflection_block!(options)
|
54
|
+
|
55
|
+
assign_default_block!(options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def fields(tags = nil)
|
59
|
+
if (trigger?(tags))
|
60
|
+
@fields
|
61
|
+
else
|
62
|
+
[ ]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def trigger?(tags)
|
67
|
+
if (@only)
|
68
|
+
return false if (!tags or (tags & @only).empty?)
|
69
|
+
end
|
70
|
+
|
71
|
+
if (@except)
|
72
|
+
return false if (tags and (tags & @except).any?)
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def assignments(model, create_options, tags)
|
79
|
+
unless (trigger?(tags))
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
if (@force)
|
84
|
+
return @fields
|
85
|
+
end
|
86
|
+
|
87
|
+
# ActiveRecord::Base derived models will return an array of strings listing
|
88
|
+
# the fields that have been altered before the model is saved. This
|
89
|
+
# includes any fields that have been populated through the constructor
|
90
|
+
# call, carried through via scope, or have been altered through accessors.
|
91
|
+
if (model and model.respond_to?(:changed))
|
92
|
+
# If any of the source methods are listed as "changed", then this is
|
93
|
+
# interpreted as a hit, that the fields are already defined or will be.
|
94
|
+
if ((@source_methods & model.changed.collect(&:to_sym)).any?)
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# If this operation does not populate any fields, then there's no further
|
100
|
+
# testing required. The processing can continue without assignments.
|
101
|
+
unless (@fields)
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
if (create_options)
|
106
|
+
# If any of the source keys are listed in the options, then this is
|
107
|
+
# interpreted as a hit, that the fields are already defined or will be.
|
108
|
+
if ((@source_keys & create_options.keys.collect(&:to_sym)).any?)
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Unless there's a model defined, at this point, there's nothing else to
|
114
|
+
# test and exclude defaults or assignments.
|
115
|
+
unless (model)
|
116
|
+
return @fields
|
117
|
+
end
|
118
|
+
|
119
|
+
# If there are potentially unassigned fields, the only way to proceed is
|
120
|
+
# to narrow it down to the ones that are still `nil`. The fields that are
|
121
|
+
# mapped to columns are considered unpopulated or set with defaults because
|
122
|
+
# of the previous tests. The remainder need to be tested by calling send.
|
123
|
+
columns = model.class.column_names.collect(&:to_sym)
|
124
|
+
|
125
|
+
applies_to_fields =@fields.select do |field|
|
126
|
+
columns.include?(field) or model.__send__(field).nil?
|
127
|
+
end
|
128
|
+
|
129
|
+
# If any of these fields need to be assigned, then that's the next step.
|
130
|
+
if (applies_to_fields.any?)
|
131
|
+
return applies_to_fields
|
132
|
+
end
|
133
|
+
|
134
|
+
# Otherwise there are fields defined, but none of them need to be assigned,
|
135
|
+
# so the operation is not necessary.
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
# Called to apply this operation. The model, create_options and tags
|
140
|
+
# arguments can be specified to provide more context.
|
141
|
+
def apply!(model, create_options, tags)
|
142
|
+
_assignments = assignments(model, create_options, tags)
|
143
|
+
|
144
|
+
return if (_assignments === false)
|
145
|
+
|
146
|
+
value = nil
|
147
|
+
|
148
|
+
# The defined blocks are tried in sequence until one of them returns
|
149
|
+
# a non-nil value.
|
150
|
+
@blocks.find do |block|
|
151
|
+
value =
|
152
|
+
case (block.arity)
|
153
|
+
when 0
|
154
|
+
block.call
|
155
|
+
when 1
|
156
|
+
block.call(model)
|
157
|
+
when 2
|
158
|
+
block.call(model, _assignments)
|
159
|
+
when 3
|
160
|
+
block.call(model, _assignments, tags)
|
161
|
+
else
|
162
|
+
block.call(model, _assignments, tags, create_options)
|
163
|
+
end
|
164
|
+
|
165
|
+
!value.nil?
|
166
|
+
end
|
167
|
+
|
168
|
+
return unless (_assignments)
|
169
|
+
|
170
|
+
model and !value.nil? and _assignments.each do |field|
|
171
|
+
next unless (field)
|
172
|
+
|
173
|
+
model.__send__(:"#{field}=", value)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
def flatten_any(array)
|
179
|
+
array = [ array ].flatten.compact.collect(&:to_sym)
|
180
|
+
|
181
|
+
return unless (array.any?)
|
182
|
+
|
183
|
+
array
|
184
|
+
end
|
185
|
+
|
186
|
+
def assign_block_options!(options)
|
187
|
+
return unless (options[:block])
|
188
|
+
|
189
|
+
@blocks << options[:block]
|
190
|
+
end
|
191
|
+
|
192
|
+
def assign_fields_options!(options)
|
193
|
+
@fields = options[:fields]
|
194
|
+
|
195
|
+
@source_keys = [ ]
|
196
|
+
@source_methods = [ ]
|
197
|
+
|
198
|
+
@fields and @fields.each do |field|
|
199
|
+
next unless (field)
|
200
|
+
|
201
|
+
@source_keys << field
|
202
|
+
@source_keys << field.to_s
|
203
|
+
|
204
|
+
@source_methods << field
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def assign_only_options!(options)
|
209
|
+
if (only_options = options[:only])
|
210
|
+
@only = flatten_any(only_options)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def assign_except_options!(options)
|
215
|
+
if (except_options = options[:except])
|
216
|
+
@except = flatten_any(except_options)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def assign_after_options!(options)
|
221
|
+
if (after_option = options[:after])
|
222
|
+
@after = after_option
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def assign_force_options!(options)
|
227
|
+
if (options[:force])
|
228
|
+
@force = options[:force]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def assign_with_options!(options)
|
233
|
+
if (with = options[:with])
|
234
|
+
@blocks << block_for_with_option(with)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def block_for_with_option(with)
|
239
|
+
case (with)
|
240
|
+
when Proc
|
241
|
+
with
|
242
|
+
when String, Symbol
|
243
|
+
lambda { respond_to?(with) ? send(with) : TestDummy::Helper.send(with) }
|
244
|
+
else
|
245
|
+
lambda { with }
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def assign_from_options!(options)
|
250
|
+
from = options[:from]
|
251
|
+
|
252
|
+
return unless (from)
|
253
|
+
|
254
|
+
case (from)
|
255
|
+
when Proc
|
256
|
+
@from = from
|
257
|
+
|
258
|
+
return
|
259
|
+
when Array
|
260
|
+
from.collect(&:to_sym)
|
261
|
+
when Hash
|
262
|
+
from = from.to_a
|
263
|
+
when String
|
264
|
+
from = from.split('.')
|
265
|
+
else
|
266
|
+
raise TestDummy::Exception, "Argument to :from must be a String, Array or Hash."
|
267
|
+
end
|
268
|
+
|
269
|
+
@blocks << lambda do |model|
|
270
|
+
from.inject(model) do |_model, _method|
|
271
|
+
_model ? _model.send(_method) : nil
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def block_for_inherit_options(spec)
|
277
|
+
case (spec)
|
278
|
+
when Proc
|
279
|
+
return spec
|
280
|
+
when Array
|
281
|
+
spec.collect(&:to_sym)
|
282
|
+
when String
|
283
|
+
spec = spec.split('.').collect(&:to_sym)
|
284
|
+
when Symbol
|
285
|
+
spec = [ spec ]
|
286
|
+
end
|
287
|
+
|
288
|
+
lambda do |model|
|
289
|
+
spec.inject(model) do |_model, _method|
|
290
|
+
_model ? _model.send(_method) : nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def assign_reflection_options!(options)
|
296
|
+
return unless (options[:foreign_key])
|
297
|
+
|
298
|
+
@model_class = options[:model_class]
|
299
|
+
@foreign_key = options[:foreign_key]
|
300
|
+
|
301
|
+
@source_keys << @foreign_key
|
302
|
+
@source_keys << @foreign_key.to_s
|
303
|
+
|
304
|
+
@source_keys.uniq!
|
305
|
+
|
306
|
+
@source_methods << @foreign_key
|
307
|
+
|
308
|
+
@source_methods.uniq!
|
309
|
+
end
|
310
|
+
|
311
|
+
def assign_inherit_options!(options)
|
312
|
+
inherit = options[:inherit]
|
313
|
+
|
314
|
+
return unless (inherit)
|
315
|
+
|
316
|
+
case (inherit)
|
317
|
+
when Hash
|
318
|
+
# Use as-is
|
319
|
+
when Symbol, String
|
320
|
+
inherit = { inherit.to_sym => [ inherit.to_sym ] }
|
321
|
+
else
|
322
|
+
# Not supported, should raise.
|
323
|
+
end
|
324
|
+
|
325
|
+
@inherited_attributes =
|
326
|
+
inherit.collect do |attribute, spec|
|
327
|
+
[
|
328
|
+
attribute.to_sym,
|
329
|
+
block_for_inherit_options(spec)
|
330
|
+
]
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def assign_reflection_block!(options)
|
335
|
+
return unless (@model_class)
|
336
|
+
|
337
|
+
@blocks <<
|
338
|
+
if (@inherited_attributes)
|
339
|
+
lambda do |model|
|
340
|
+
@model_class.create_dummy do |reflection_model|
|
341
|
+
@inherited_attributes.each do |attribute, proc|
|
342
|
+
reflection_model.send("#{attribute}=", proc.call(model))
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
else
|
347
|
+
lambda do |model|
|
348
|
+
@model_class.create_dummy
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def assign_default_block!(options)
|
354
|
+
return if (@blocks.any? or !@fields or @fields.empty?)
|
355
|
+
|
356
|
+
@fields.each do |field|
|
357
|
+
next unless (field and TestDummy::Helper.respond_to?(field))
|
358
|
+
|
359
|
+
@blocks << lambda do |model|
|
360
|
+
TestDummy::Helper.send(field)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|