test_dummy 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9bb28b5683535e7a1fab0bf70b6e0feb4a43162a
|
4
|
+
data.tar.gz: b0cb2df9f0fe1e07986be13f7770b6b79fd351ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9cf8168eddb5a330444ea1c9801293b8c3f17930d603ef0166361bd822b06f030490d39616d465c659df499ed5bcf2a4c83c14790f9131c7372ed1c07505976b
|
7
|
+
data.tar.gz: 22625b5b235e9a00a2a05c9dad12da40228784048e01d570e15cc6d6cc894773672d1daa87dc27dd1e9e6f027f50b10fa95f021010891714e6f1a3fa10a9fac3
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -21,7 +21,9 @@ declare how to dummy something.
|
|
21
21
|
To add Test Dummy functionality to an application, add the dependency to the
|
22
22
|
`Gemfile`:
|
23
23
|
|
24
|
-
|
24
|
+
```ruby
|
25
|
+
gem 'test_dummy'
|
26
|
+
```
|
25
27
|
|
26
28
|
Most application frameworks provide some kind of test helper foundation,
|
27
29
|
like `test/test_helper.rb` in Rails or `test/helper.rb` in many gem templates.
|
data/Rakefile
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
|
4
|
+
require 'bundler/setup'
|
5
|
+
|
4
6
|
begin
|
5
7
|
require 'jeweler'
|
8
|
+
|
6
9
|
Jeweler::Tasks.new do |gem|
|
7
10
|
gem.name = "test_dummy"
|
8
11
|
gem.summary = %q[Quick test data generator and fake model maker]
|
@@ -11,16 +14,16 @@ begin
|
|
11
14
|
gem.homepage = "http://github.com/tadman/test_dummy"
|
12
15
|
gem.authors = %w[ tadman ]
|
13
16
|
end
|
17
|
+
|
14
18
|
Jeweler::GemcutterTasks.new
|
15
19
|
rescue LoadError
|
16
20
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
17
21
|
end
|
18
22
|
|
19
23
|
require 'rake/testtask'
|
20
|
-
|
21
|
-
|
22
|
-
test.pattern = 'test
|
23
|
-
test.verbose = true
|
24
|
+
|
25
|
+
Rake::TestTask.new do |test|
|
26
|
+
test.pattern = 'test/unit/test_*.rb'
|
24
27
|
end
|
25
28
|
|
26
29
|
task :default => :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/test_dummy.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module TestDummy
|
2
2
|
# == Submodules ============================================================
|
3
3
|
|
4
|
+
autoload(:Definition, 'test_dummy/definition')
|
4
5
|
autoload(:Helper, 'test_dummy/helper')
|
6
|
+
autoload(:Loader, 'test_dummy/loader')
|
7
|
+
autoload(:Operation, 'test_dummy/operation')
|
5
8
|
autoload(:Support, 'test_dummy/support')
|
6
9
|
autoload(:TestHelper, 'test_dummy/test_helper')
|
7
10
|
|
@@ -39,11 +42,14 @@ module TestDummy
|
|
39
42
|
@dummy_extensions_path = value
|
40
43
|
end
|
41
44
|
|
45
|
+
# This is called when this module is included.
|
42
46
|
def self.included(base)
|
43
47
|
base.send(:extend, ClassMethods)
|
44
48
|
base.send(:include, InstanceMethods)
|
45
49
|
end
|
46
50
|
|
51
|
+
# Used to dynamically declare extensions on a particular class. Has the
|
52
|
+
# effect of executing the block in the context of the class given.
|
47
53
|
def self.declare(on_class, &block)
|
48
54
|
on_class.instance_eval(&block)
|
49
55
|
end
|
@@ -53,7 +59,7 @@ module TestDummy
|
|
53
59
|
Helper.send(:extend, new_module)
|
54
60
|
end
|
55
61
|
|
56
|
-
# Used to configure defaults or aliases that can be used by all
|
62
|
+
# Used to configure defaults or aliases that can be used by all operations.
|
57
63
|
# Takes a block that should call definition methods like `dummy`.
|
58
64
|
def self.define(&block)
|
59
65
|
instance_eval(&block)
|
@@ -61,16 +67,17 @@ module TestDummy
|
|
61
67
|
|
62
68
|
# Used in an initializer to define things that can be dummied by all
|
63
69
|
# models if these properties are available.
|
64
|
-
def self.dummy(*
|
65
|
-
case (
|
70
|
+
def self.dummy(*fields, &block)
|
71
|
+
case (fields.last)
|
66
72
|
when Hash
|
67
|
-
options =
|
73
|
+
options = fields.pop
|
68
74
|
end
|
69
|
-
|
75
|
+
|
76
|
+
# REFACTOR: Adapt to new Operation style
|
70
77
|
if (options and options[:with])
|
71
78
|
with = options[:with]
|
72
79
|
|
73
|
-
|
80
|
+
fields.each do |name|
|
74
81
|
if (Helper.respond_to?(with))
|
75
82
|
Helper.send(:alias_method, name, with)
|
76
83
|
else
|
@@ -80,7 +87,7 @@ module TestDummy
|
|
80
87
|
end
|
81
88
|
end
|
82
89
|
else
|
83
|
-
|
90
|
+
fields.each do |name|
|
84
91
|
Helper.send(:define_method, name, &block)
|
85
92
|
end
|
86
93
|
end
|
@@ -94,175 +101,55 @@ module TestDummy
|
|
94
101
|
module ClassMethods
|
95
102
|
# Returns a Hash which describes the dummy configuration for this
|
96
103
|
# Model class.
|
97
|
-
def
|
98
|
-
@
|
104
|
+
def dummy_definition
|
105
|
+
@dummy_definition ||= TestDummy::Definition.new
|
106
|
+
|
107
|
+
TestDummy::Loader.load!(self)
|
108
|
+
|
109
|
+
@dummy_definition
|
99
110
|
end
|
100
111
|
|
101
112
|
# Declares how to fake one or more attributes. Accepts a block
|
102
113
|
# that can receive up to two parameters, the first the instance of
|
103
114
|
# the model being created, the second the parameters supplied to create
|
104
115
|
# it. The first and second parameters may be nil.
|
105
|
-
def dummy(*
|
106
|
-
options =
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
@test_dummy ||= { }
|
114
|
-
@test_dummy_order ||= [ ]
|
115
|
-
@test_dummy_tags ||= { }
|
116
|
-
|
117
|
-
names.flatten.each do |name|
|
118
|
-
name = name.to_sym
|
119
|
-
from = nil
|
120
|
-
create_options_proc = nil
|
121
|
-
|
122
|
-
if (options)
|
123
|
-
if (options[:only])
|
124
|
-
tags = [ options[:only] ].flatten.compact
|
125
|
-
|
126
|
-
if (tags.any?)
|
127
|
-
set = @test_dummy_tags[name] ||= { }
|
128
|
-
|
129
|
-
set[:only] = tags
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
if (options[:except])
|
134
|
-
tags = [ options[:except] ].flatten.compact
|
135
|
-
|
136
|
-
if (tags.any?)
|
137
|
-
set = @test_dummy_tags[name] ||= { }
|
138
|
-
|
139
|
-
set[:except] = tags
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
if (options[:with])
|
144
|
-
if (block)
|
145
|
-
raise TestDummy::Exception, "Cannot use block and :with option at the same time."
|
146
|
-
end
|
147
|
-
|
148
|
-
block =
|
149
|
-
case (with = options[:with])
|
150
|
-
when Proc
|
151
|
-
with
|
152
|
-
when String, Symbol
|
153
|
-
lambda { send(with) }
|
154
|
-
else
|
155
|
-
lambda { with }
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# The :inherit directive is used to pass arguments through to the
|
160
|
-
# create_dummy call on the association's class.
|
161
|
-
if (inherit = options[:inherit])
|
162
|
-
inherit_options = Hash[
|
163
|
-
inherit.collect do |attribute, spec|
|
164
|
-
[
|
165
|
-
attribute.to_sym,
|
166
|
-
case (spec)
|
167
|
-
when Array
|
168
|
-
spec.collect(&:to_sym)
|
169
|
-
when String
|
170
|
-
spec.split('.').collect(&:to_sym)
|
171
|
-
when Proc
|
172
|
-
spec
|
173
|
-
end
|
174
|
-
]
|
175
|
-
end
|
176
|
-
]
|
177
|
-
|
178
|
-
create_options_proc = lambda do |target, model, with_attributes|
|
179
|
-
inherit_options.each do |attribute, spec|
|
180
|
-
target[attribute] ||=
|
181
|
-
case (spec)
|
182
|
-
when Array
|
183
|
-
spec.inject(model) do |_model, _method|
|
184
|
-
_model ? _model.send(_method) : nil
|
185
|
-
end
|
186
|
-
when Proc
|
187
|
-
proc.call(model, with_attributes)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
if (from = options[:from])
|
194
|
-
if (block)
|
195
|
-
raise TestDummy::Exception, "Cannot use block, :with, or :from option at the same time."
|
196
|
-
end
|
197
|
-
|
198
|
-
case (from)
|
199
|
-
when Array
|
200
|
-
# Already in correct form
|
201
|
-
when Hash
|
202
|
-
from = from.to_a
|
203
|
-
when String
|
204
|
-
from = from.split('.')
|
205
|
-
else
|
206
|
-
raise TestDummy::Exception, "Argument to :from must be a String, Array or Hash."
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
reflection_class, foreign_key = TestDummy::Support.reflection_properties(self, name)
|
211
|
-
|
212
|
-
if (reflection_class and foreign_key)
|
213
|
-
block = lambda do |model, with_attributes|
|
214
|
-
unless ((with_attributes and (with_attributes.key?(name) or with_attributes.key?(foreign_key))) or model.send(name).present?)
|
215
|
-
object = from && from.inject(model) do |_model, _method|
|
216
|
-
_model ? _model.send(_method) : nil
|
217
|
-
end
|
218
|
-
|
219
|
-
object ||=
|
220
|
-
reflection_class.create_dummy(with_attributes) do |target|
|
221
|
-
if (create_options_proc)
|
222
|
-
create_options_proc.call(target, model, with_attributes)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
model.send(:"#{name}=", object)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
116
|
+
def dummy(*fields)
|
117
|
+
options =
|
118
|
+
case (fields.last)
|
119
|
+
when Hash
|
120
|
+
fields.pop
|
121
|
+
else
|
122
|
+
{ }
|
230
123
|
end
|
231
124
|
|
232
|
-
|
233
|
-
|
234
|
-
# the to_dummy call. Leave placeholder (true) instead.
|
235
|
-
|
236
|
-
@test_dummy[name] = block || true
|
237
|
-
@test_dummy_order << name
|
125
|
+
if (block_given?)
|
126
|
+
options = options.merge(:block => Proc.new)
|
238
127
|
end
|
128
|
+
|
129
|
+
self.dummy_definition.define_operation(self, fields, options)
|
239
130
|
end
|
240
131
|
|
241
|
-
# Returns true if all the supplied attribute
|
132
|
+
# Returns true if all the supplied attribute fields have defined
|
242
133
|
# dummy methods, or false otherwise.
|
243
|
-
def can_dummy?(*
|
244
|
-
@test_dummy
|
245
|
-
|
246
|
-
names.flatten.reject do |name|
|
247
|
-
@test_dummy.key?(name)
|
248
|
-
end.empty?
|
134
|
+
def can_dummy?(*fields)
|
135
|
+
@test_dummy and @test_dummy.can_dummy?(*fields) or false
|
249
136
|
end
|
250
137
|
|
251
138
|
# Builds a dummy model with some parameters set as supplied. The
|
252
139
|
# new model is provided to the optional block for manipulation before
|
253
140
|
# the dummy operation is completed. Returns a dummy model which has not
|
254
141
|
# been saved.
|
255
|
-
def build_dummy(
|
256
|
-
|
257
|
-
|
258
|
-
|
142
|
+
def build_dummy(create_attributes = nil, tags = nil)
|
143
|
+
build_scope = where(nil)
|
144
|
+
|
145
|
+
create_attributes = TestDummy::Support.combine_attributes(build_scope, create_attributes)
|
259
146
|
|
260
|
-
model = new(
|
147
|
+
model = new(create_attributes)
|
261
148
|
|
262
149
|
yield(model) if (block_given?)
|
263
150
|
|
264
|
-
self.
|
265
|
-
|
151
|
+
self.dummy_definition.apply!(model, create_attributes, tags)
|
152
|
+
|
266
153
|
model
|
267
154
|
end
|
268
155
|
|
@@ -271,14 +158,16 @@ module TestDummy
|
|
271
158
|
# the dummy operation is completed and the model is saved. Returns a
|
272
159
|
# dummy model. The model may not have been saved if there was a
|
273
160
|
# validation failure, or if it was blocked by a callback.
|
274
|
-
def create_dummy(*
|
275
|
-
if (
|
276
|
-
|
161
|
+
def create_dummy(*tags, &block)
|
162
|
+
if (tags.last.is_a?(Hash))
|
163
|
+
create_attributes = tags.pop
|
277
164
|
end
|
278
165
|
|
279
|
-
model = build_dummy(
|
280
|
-
|
166
|
+
model = build_dummy(create_attributes, tags, &block)
|
167
|
+
|
281
168
|
model.save
|
169
|
+
|
170
|
+
self.dummy_definition.apply_after_save!(model, create_attributes, tags)
|
282
171
|
|
283
172
|
model
|
284
173
|
end
|
@@ -289,166 +178,26 @@ module TestDummy
|
|
289
178
|
# dummy model. Will throw ActiveRecord::RecordInvalid if there was al20
|
290
179
|
# validation failure, or ActiveRecord::RecordNotSaved if the save was
|
291
180
|
# blocked by a callback.
|
292
|
-
def create_dummy!(*
|
293
|
-
if (
|
294
|
-
|
181
|
+
def create_dummy!(*tags, &block)
|
182
|
+
if (tags.last.is_a?(Hash))
|
183
|
+
create_attributes = tags.pop
|
295
184
|
end
|
296
185
|
|
297
|
-
model = build_dummy(
|
186
|
+
model = build_dummy(create_attributes, tags, &block)
|
298
187
|
|
299
188
|
model.save!
|
300
189
|
|
301
|
-
model
|
302
|
-
end
|
303
|
-
|
304
|
-
# Produces dummy data for a single attribute.
|
305
|
-
def dummy_attribute(name, with_attributes = nil)
|
306
|
-
with_attributes = TestDummy.combine_attributes(scoped.scope_for_create, with_attributes)
|
307
|
-
|
308
|
-
dummy_method_call(nil, with_attributes, dummy_method(name))
|
309
|
-
end
|
310
|
-
|
311
|
-
# Produces a complete set of dummy attributes. These can be used to
|
312
|
-
# create a model.
|
313
|
-
def dummy_attributes(with_attributes = nil, tags = nil)
|
314
|
-
with_attributes = TestDummy.combine_attributes(scoped.scope_for_create, with_attributes)
|
315
|
-
|
316
|
-
@test_dummy_order.each do |field|
|
317
|
-
next if (with_attributes.key?(field))
|
190
|
+
self.dummy_definition.apply_after_save!(model, create_attributes, tags)
|
318
191
|
|
319
|
-
if (when_tagged = @test_dummy_when[field])
|
320
|
-
next if (!tags or (tags & when_tagged).empty?)
|
321
|
-
end
|
322
|
-
|
323
|
-
result = dummy(field, with_attributes)
|
324
|
-
|
325
|
-
case (result)
|
326
|
-
when nil, with_attributes
|
327
|
-
# Declined to populate parameters if method returns nil
|
328
|
-
# or returns the existing parameter set.
|
329
|
-
else
|
330
|
-
with_attributes[field] = result
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
with_attributes
|
335
|
-
end
|
336
|
-
|
337
|
-
# This performs the dummy operation on a model with an optional set
|
338
|
-
# of parameters.
|
339
|
-
def execute_dummy_operation(model, with_attributes = nil, tags = nil)
|
340
|
-
load_dummy_declaration!
|
341
|
-
|
342
|
-
return model unless (@test_dummy_order)
|
343
|
-
|
344
|
-
@test_dummy_order.each do |name|
|
345
|
-
if (tag_conditions = @test_dummy_tags[name])
|
346
|
-
if (required_tags = tag_conditions[:only])
|
347
|
-
next if (!tags or (tags & required_tags).empty?)
|
348
|
-
end
|
349
|
-
|
350
|
-
if (excluding_tags = tag_conditions[:except])
|
351
|
-
next if (tags and (tags & excluding_tags).any?)
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
if (respond_to?(:reflect_on_association) and reflection = reflect_on_association(name))
|
356
|
-
foreign_key = (reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name).to_sym
|
357
|
-
|
358
|
-
unless ((with_attributes and (with_attributes.key?(name.to_sym) or with_attributes.key?(foreign_key.to_sym))) or model.send(name).present?)
|
359
|
-
model.send(:"#{name}=", dummy_method_call(model, with_attributes, dummy_method(name)))
|
360
|
-
end
|
361
|
-
elsif (respond_to?(:association_reflection) and reflection = association_reflection(name))
|
362
|
-
key = reflection[:key] || :"#{name.to_s.underscore}_id"
|
363
|
-
|
364
|
-
unless ((with_attributes and (with_attributes.key?(name.to_sym) or with_attributes.key?(key.to_sym))) or model.send(name).present?)
|
365
|
-
model.send(:"#{name}=", dummy_method_call(model, with_attributes, dummy_method(name)))
|
366
|
-
end
|
367
|
-
else
|
368
|
-
unless (with_attributes and (with_attributes.key?(name.to_sym) or with_attributes.key?(name.to_s)))
|
369
|
-
model.send(:"#{name}=", dummy_method_call(model, with_attributes, dummy_method(name)))
|
370
|
-
end
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
192
|
model
|
375
193
|
end
|
376
|
-
|
377
|
-
protected
|
378
|
-
def load_dummy_declaration!
|
379
|
-
return unless (@_dummy_module.nil?)
|
380
|
-
|
381
|
-
@_dummy_module =
|
382
|
-
begin
|
383
|
-
dummy_path = File.expand_path(
|
384
|
-
"#{name.underscore}.rb",
|
385
|
-
TestDummy.dummy_extensions_path
|
386
|
-
)
|
387
|
-
|
388
|
-
if (File.exist?(dummy_path))
|
389
|
-
load(dummy_path)
|
390
|
-
end
|
391
|
-
rescue LoadError
|
392
|
-
# Persist that this load attempt failed and don't retry later.
|
393
|
-
false
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
def dummy_method_call(model, with_attributes, block)
|
398
|
-
case (block.arity)
|
399
|
-
when 2
|
400
|
-
block.call(model, with_attributes)
|
401
|
-
when 1
|
402
|
-
block.call(model)
|
403
|
-
else
|
404
|
-
model.instance_eval(&block)
|
405
|
-
end
|
406
|
-
end
|
407
|
-
|
408
|
-
def dummy_method(name)
|
409
|
-
name = name.to_sym
|
410
|
-
|
411
|
-
block = @test_dummy[name]
|
412
|
-
|
413
|
-
case (block)
|
414
|
-
when Module
|
415
|
-
block.method(name)
|
416
|
-
when Symbol
|
417
|
-
Helper.method(name)
|
418
|
-
when true
|
419
|
-
# Configure association dummy the first time it is called
|
420
|
-
if (respond_to?(:reflect_on_association) and reflection = reflect_on_association(name))
|
421
|
-
foreign_key = (reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name).to_sym
|
422
|
-
|
423
|
-
@test_dummy[name] =
|
424
|
-
lambda do |model, with_attributes|
|
425
|
-
(with_attributes and (with_attributes.key?(foreign_key) or with_attributes.key?(name))) ? nil : reflection.klass.send(:create_dummy)
|
426
|
-
end
|
427
|
-
elsif (respond_to?(:association_reflection) and reflection = association_reflection(name))
|
428
|
-
key = reflection[:key] || :"#{name.to_s.underscore}_id"
|
429
|
-
|
430
|
-
@test_dummy[name] =
|
431
|
-
lambda do |model, with_attributes|
|
432
|
-
(with_attributes and (with_attributes.key?(key) or with_attributes.key?(name))) ? nil : reflection[:associated_class].send(:create_dummy)
|
433
|
-
end
|
434
|
-
elsif (TestDummy::Helper.respond_to?(name))
|
435
|
-
@test_dummy[name] = lambda do |model, with_attributes|
|
436
|
-
TestDummy::Helper.send(name)
|
437
|
-
end
|
438
|
-
else
|
439
|
-
raise "Cannot dummy unknown relationship #{name}"
|
440
|
-
end
|
441
|
-
else
|
442
|
-
block
|
443
|
-
end
|
444
|
-
end
|
445
194
|
end
|
446
195
|
|
447
196
|
module InstanceMethods
|
448
197
|
# Assigns any attributes which can be dummied that have not already
|
449
198
|
# been populated.
|
450
|
-
def dummy!(
|
451
|
-
self.class.
|
199
|
+
def dummy!(create_attributes = nil, tags = nil)
|
200
|
+
self.class.dummy_definition.apply!(self, create_attributes, tags)
|
452
201
|
end
|
453
202
|
end
|
454
203
|
end
|