mattscilipoti-model_steps 0.2.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+
7
+ #IDE
8
+ .idea
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-10 Matt Scilipoti
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = mattscilipoti-model_steps
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but
13
+ bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ == Copyright
17
+
18
+ Copyright (c) 2009-10 Matt Scilipoti. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "mattscilipoti-model_steps"
8
+ gem.summary = %Q{Model Steps for cucumber}
9
+ gem.description = %Q{Model Steps for cucumber}
10
+ gem.email = "matt@scilipoti.name"
11
+ gem.homepage = "http://github.com/mattscilipoti/mattscilipoti-model_steps"
12
+ gem.authors = ["Matt Scilipoti"]
13
+ gem.add_development_dependency "micronaut"
14
+ gem.add_development_dependency "cucumber"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'micronaut/rake_task'
23
+ Micronaut::RakeTask.new(:examples) do |examples|
24
+ examples.pattern = 'examples/**/*_example.rb'
25
+ examples.ruby_opts << '-Ilib -Iexamples'
26
+ end
27
+
28
+ Micronaut::RakeTask.new(:rcov) do |examples|
29
+ examples.pattern = 'examples/**/*_example.rb'
30
+ examples.rcov_opts = '-Ilib -Iexamples'
31
+ examples.rcov = true
32
+ end
33
+
34
+ task :examples => :check_dependencies
35
+
36
+ begin
37
+ require 'cucumber/rake/task'
38
+ Cucumber::Rake::Task.new(:features)
39
+
40
+ task :features => :check_dependencies
41
+ rescue LoadError
42
+ task :features do
43
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
44
+ end
45
+ end
46
+
47
+ task :default => :examples
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ if File.exist?('VERSION')
52
+ version = File.read('VERSION')
53
+ else
54
+ version = ""
55
+ end
56
+
57
+ rdoc.rdoc_dir = 'rdoc'
58
+ rdoc.title = "mattscilipoti-model_steps #{version}"
59
+ rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('lib/**/*.rb')
61
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'micronaut'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+
7
+ require 'mattscilipoti-model_steps'
8
+
9
+ def not_in_editor?
10
+ !(ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM'))
11
+ end
12
+
13
+ Micronaut.configure do |c|
14
+ c.color_enabled = not_in_editor?
15
+ c.filter_run :focused => true
16
+ end
17
+
@@ -0,0 +1,7 @@
1
+ require 'example_helper'
2
+
3
+ describe "ModelSteps", 'upon require it will require all files in model_steps/step_definitions" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
2
+
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ #require 'model_steps/step_definitions'
3
+
4
+ require 'micronaut/expectations'
5
+
6
+ World(Micronaut::Matchers)
@@ -0,0 +1,3 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
@@ -0,0 +1,761 @@
1
+ Cucumber::Ast::Table.class_eval do
2
+ def is_date_column?(column_name)
3
+ column_name.columnify =~ /( at|_at|time|date)$/
4
+ end
5
+
6
+ def chronic_parsable_columns
7
+ chronic_parsable_columns = []
8
+ headers.each do |col|
9
+ next unless is_date_column?(col)
10
+
11
+ chronic_parsable_columns << col
12
+ chronic_parsable_columns << col.titleize
13
+ end
14
+ return chronic_parsable_columns
15
+ end
16
+
17
+ def map_chronic_columns!
18
+ self.map_columns!(chronic_parsable_columns) do |cell_value|
19
+ if cell_value.blank?
20
+ cell_value
21
+ else
22
+ parsed_value = Chronic.parse(cell_value)
23
+ raise "Chronic can not parse '#{cell_value}' to a date/time." unless parsed_value
24
+ parsed_value.to_s
25
+ end
26
+ end
27
+ end
28
+
29
+ def map_columns!(headers_to_map)
30
+ existing_headers = self.headers & headers_to_map
31
+ existing_headers.each do |header|
32
+ self.map_column!(header) { |cell_value| yield cell_value }
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+
39
+ #This file contains steps which work with rails' models, but are not for a specific model
40
+ Then /^I should see no (\D+)$/ do |requested_model|
41
+ Then "I should see 0 #{requested_model}"
42
+ end
43
+
44
+ Then /^I should see (\d+) (\D+)$/ do |expected_count, requested_model|
45
+ expected_count = expected_count.to_i
46
+ css_class = requested_model.underscore
47
+ if expected_count > 0
48
+ response.should have_tag("table.#{css_class}") do
49
+ with_tag("tbody tr", expected_count)
50
+ end
51
+ else
52
+ #response.should have_tag("div.#{css_class}", translate(:none_found)) #TODO: undefined method `translate' for #<ActionController::Integration::Session:0xb4f3e9e0> (NoMethodError)
53
+ response.should have_tag("div.#{css_class}", 'None found')
54
+ end
55
+
56
+ end
57
+
58
+ Then /^I should see (?:these|the following) (\D+):$/ do |requested_model, table|
59
+ # table is a Cucumber::Ast::Table
60
+ table.map_chronic_columns!
61
+ #WORKAROUND: why does table.diff! expect Trouble to be nil (vs. '')?
62
+ # table.map_columns!(['Trouble']) {|trouble_message| trouble_message == '\nil' ? nil : trouble_message}
63
+
64
+ mapped_table = map_table_headers(table)
65
+
66
+ requested_table = (requested_model =~ /^(.+)[(](.+)[)]$/) ? $1 : requested_model
67
+
68
+ css_class = requested_table.pluralize.underscore
69
+
70
+ html_table = table(tableish("table.#{css_class} tr", 'td,th'))
71
+
72
+ mapped_table.diff!(html_table)
73
+ end
74
+
75
+
76
+ #Given the following Camera Events exist:
77
+ #Note: use ((?!.*should) to avoid conflicts with
78
+ # Given the following Camera Events should exist:
79
+ Given /^(?:these|the following) (?!.*should)(.+) exist:$/ do |requested_model, table|
80
+
81
+ # table is a Cucumber::Ast::Table
82
+ model = requested_model_to_model(requested_model)
83
+
84
+ map_table_columns!(table)
85
+ mapped_table = map_table_headers(table)
86
+ mapped_table.hashes.each do |table_row|
87
+
88
+ requested_params = table_row.dup
89
+
90
+ model_params = requested_params_to_model_params(requested_params, model)
91
+
92
+ model_under_test = Factory.create(model_to_factory_symbol(model.name), model_params)
93
+
94
+ if model_under_test.is_a?(ImportSession)
95
+ Traffipax.deprecated("special fixture code for 'the following Import Sessions exist'", 'only until ImportSession no longer has ImportSteps') unless ImportSession.new.respond_to?('import_steps')
96
+ trouble = model_params[:trouble]
97
+ #ensure last step has proper status
98
+ #TODO: this is a smell. We are bypassing proper operation. Is this test appropriate?
99
+ model_under_test.current_step.update_attributes!(:started_at => model_under_test.started_at, :completed_at => model_under_test.completed_at)
100
+ model_under_test.current_step.trouble = trouble if trouble
101
+ end
102
+
103
+ model_under_test
104
+ end
105
+
106
+ end
107
+
108
+ #Given ModelA:unique_ident exists
109
+ # Create a new ModelA with default_identifier_column = default_identifier
110
+ Given /^(.+):(.+) exists$/ do |requested_model, default_identifier|
111
+ model = requested_model_to_model(requested_model)
112
+ column_name = model.default_identifier_column
113
+ Factory.create(model_to_factory_symbol(requested_model), column_name => default_identifier)
114
+ end
115
+
116
+ #Given ModelA:DI has 3 ModelBs
117
+ Given /^(.+):(.+) has (\d+) (?!.*with:)(.+)$/ do |requested_model, default_identifier, association_quantity, requested_association_name|
118
+ association_quantity = association_quantity.to_i
119
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
120
+ create_requested_model_associations(model_under_test, association_quantity, requested_association_name.pluralize)
121
+ end
122
+
123
+ #Given ModelA:DI has 3 ModelBs with:
124
+ # |attribute_name|
125
+ # |attribute_name|
126
+ # |value |
127
+ Given /^(\D+):(.+) has (\d+) (\D+) with:$/ do |requested_model, default_identifier, association_quantity, requested_association_name, table|
128
+ association_quantity = association_quantity.to_i
129
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
130
+ # default_params = table ? table.hashes.first : {}
131
+ default_params = table.hashes.first
132
+ create_requested_model_associations(model_under_test, association_quantity, requested_association_name.pluralize, [], default_params)
133
+ end
134
+
135
+ #7/09: find_or_create was not creating. Validations? Use Factory?
136
+ ##Given ModelA:DI_A has ModelB:DI_B
137
+ Given /^(\D+):(.+) has (\D+):(.+)$/ do |requested_model, default_identifier, association_model_name, associated_model_default_identifier|
138
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
139
+ associated_model = requested_model_to_model(association_model_name)
140
+ factory_name = model_to_factory_symbol(associated_model)
141
+ associated_model_under_test = associated_model.find_by_default_identifier(default_identifier) || Factory(factory_name, associated_model.default_identifier_column => default_identifier)
142
+
143
+ possible_associations = [association_model_name.underscore, association_model_name.pluralize.underscore]
144
+ association_name = possible_associations.detect {|association_name| model_under_test.respond_to?(association_name)}
145
+
146
+ if association_name
147
+ if association_name.pluralize == association_name
148
+ associated_items = model_under_test.send(association_name)
149
+ associated_items << associated_model_under_test
150
+ else
151
+ model_under_test.send(association_name + '=', associated_model_under_test)
152
+ end
153
+ model_under_test.save!
154
+
155
+ else
156
+ raise "Neither of these associations exist for #{model_under_test.class.name}: #{possible_associations.inspect}"
157
+ end
158
+ end
159
+
160
+ Given /^(.+):(.+) (?:has|had) (?:these|this|the following) attributes:$/ do |requested_model, default_identifier, table|
161
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
162
+
163
+ attributes = map_table_headers(table).hashes.first
164
+ model_under_test.update_attributes!(attributes)
165
+ end
166
+
167
+ #Given ModelA has the following existing ModelB's (see table)
168
+ # Finds the ModelB's which match the conditions
169
+ # And assigns themto ModelA.association
170
+ Given /^(\D+):(.+) has (?:these|the following) existing (\D+):$/ do |requested_model, default_identifier, requested_association_name, table|
171
+ association_quantity = table.rows.size
172
+
173
+ map_table_columns!(table)
174
+ array_of_requested_params = table.hashes
175
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
176
+ assign_requested_model_associations(model_under_test, association_quantity, requested_association_name, array_of_requested_params)
177
+ end
178
+
179
+ #Given ModelA has this ModelB (see table)
180
+ #Given ModelA has these ModelBs (see table)
181
+ #Given ModelA has the following ModelBs (see table)
182
+ #Given ModelA has these ModelBs(factory suffix) (see table)
183
+ Given /^(.+):(.+) (?:has|had) (?:these|this|the following) (?!existing |attributes)(.+)?:$/ do |requested_model, default_identifier, requested_association_name, table|
184
+ #needs negative look behind (?!existing) for "has these existing ModelBs
185
+ #needs negative look behind (?!existing|attributes) AND optional (.+)? for "has these attributes"
186
+ association_quantity = table.rows.size
187
+
188
+ map_table_columns!(table)
189
+ array_of_requested_params = table.hashes
190
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
191
+ create_requested_model_associations(model_under_test, association_quantity, requested_association_name, array_of_requested_params)
192
+ end
193
+
194
+ Given /^(\D+):(.*) performed (?:a|an) (?!\D+:)(\D+)$/ do |requested_model, default_identifier, requested_activity|
195
+ model = requested_model_to_model(requested_model)
196
+
197
+ perform_activity(model, default_identifier, requested_activity)
198
+ end
199
+
200
+ Given /^(\D+):(.*) performed the following (\D+)(?:s?):$/ do |requested_model, default_identifier, requested_activity, table|
201
+ model = requested_model_to_model(requested_model)
202
+
203
+ activity = perform_activity(model, default_identifier, requested_activity)
204
+
205
+ map_table_columns!(table)
206
+ table.hashes.each do |params|
207
+ activity_params = params.dup
208
+ #if successful, completed = started
209
+ unless params[:completed_at] || params[:trouble]
210
+ activity_params.merge!(:completed_at => params[:started_at])
211
+ end
212
+ activity.update_attributes!(activity_params)
213
+ end
214
+
215
+ end
216
+
217
+ #Given no CameraEvents exist
218
+ #Destroys all CameraEvents
219
+ Given /^no (?!.*should)(\D+) exist$/ do |requested_models|
220
+ model_klass = requested_model_to_model(requested_models)
221
+ model_klass.destroy_all
222
+ model_klass.count.should == 0
223
+ end
224
+
225
+ #Given x CameraEvents exist
226
+ #Creates x CameraEvents using FactoryGirl
227
+ Given /^(\d+) (?!.*should)(\D+) exist$/ do |requested_count, requested_models|
228
+ requested_count = requested_count.to_i
229
+
230
+ model_klass = requested_model_to_model(requested_models)
231
+ factory_name = model_to_factory_symbol(requested_models)
232
+
233
+ requested_count.times do
234
+ Factory.create(factory_name)
235
+ end
236
+
237
+ model_klass.count.should == requested_count
238
+ end
239
+
240
+
241
+ Given /^(\D+):(.+) does not exist$/ do |requested_model, default_identifier|
242
+ begin
243
+ model = requested_model_to_model(requested_model)
244
+ instance = model.find_by_default_identifier(default_identifier)
245
+ if instance
246
+ instance.destroy if instance
247
+ instance.reload.should be_nil
248
+ end
249
+ rescue ActiveRecord::RecordNotFound
250
+ end
251
+ end
252
+
253
+ #When I navigate to the page for listing the requested model
254
+ When /^I list (.+)s$/ do |requested_model|
255
+ controller_name = requested_model.underscore.pluralize
256
+ visit send(controller_name + "_path")
257
+ end
258
+
259
+ #When I navigate to the page for showing/editing the requested model
260
+ When /^I (edit|show) (.+):(.+)$/ do |action, requested_model, default_identifier|
261
+ parent_requested_model, requested_model = requested_model.split('/') if requested_model.include?('/')
262
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
263
+
264
+ action_prefix = case action
265
+ when 'show'
266
+ ''
267
+ when 'edit'
268
+ 'edit_'
269
+ else
270
+ raise "That action (#{action}) is not currently supported."
271
+ end
272
+
273
+ #TODO: model.controllerize?
274
+ controller_name = model_under_test.class.base_class.name.underscore
275
+
276
+ if parent_requested_model
277
+ parent_model = model_under_test.send(parent_requested_model.underscore)
278
+ parent_prefix = "#{parent_requested_model.underscore}_"
279
+ named_path = action_prefix + parent_prefix + controller_name + "_path"
280
+ visit send( named_path, parent_model, model_under_test)
281
+ else
282
+ named_path = action_prefix + controller_name + "_path"
283
+ visit send( named_path, model_under_test)
284
+ end
285
+ Then "I should not see an error message"
286
+
287
+ end
288
+
289
+ When /^I fill in required (.+) fields$/ do |requested_model|
290
+ fill_in_required_fields requested_model
291
+ end
292
+
293
+ When /^I fill in required (.+) fields except:$/ do |requested_model, table|
294
+ fill_in_required_fields(requested_model, table.rows.flatten)
295
+ end
296
+
297
+ #Follow from left to right
298
+ #Finds/Creates/assigns each model consecutively
299
+ # assigning each previous_model as parent to current
300
+ # |Jurisdiciton|Location|Batch|
301
+ # also supports assigning attributes to previous model.
302
+ # |Jurisdiciton|Location|location_code|
303
+ When /^we setup the following:$/ do |table|
304
+ # table is a |US |L1 |L1_B1 |
305
+
306
+ table.rows.each do |row|
307
+ previous_model = nil
308
+
309
+ table.headers.each_with_index do |header, column_index|
310
+ value = row[column_index]
311
+
312
+ if previous_model && previous_model.respond_to?("#{header}=")
313
+ previous_model.update_attributes!(header => value)
314
+ next
315
+ end
316
+
317
+ factory_name = model_to_factory_symbol(header)
318
+ model_klass = header.classify.constantize
319
+ new_model = model_klass.find_by_default_identifier(value)
320
+
321
+ new_model_params = {}
322
+ if previous_model
323
+ parent = previous_model
324
+ parent_association = previous_model.class.name.underscore
325
+
326
+ case model_klass.name
327
+ when IncidentBatch.name #compare class to class did NOT work??
328
+ parent = previous_model.location_camera
329
+ parent_association = 'location_camera'
330
+ end
331
+ new_model_params.merge!({ parent_association => parent })
332
+ end
333
+
334
+ if new_model
335
+ new_model.update_attributes! new_model_params
336
+ else
337
+ new_model_params.merge!({ model_klass.default_identifier_column => value })
338
+ new_model = Factory.create(factory_name, new_model_params)
339
+ end
340
+
341
+ previous_model = new_model
342
+ end
343
+ end
344
+ end
345
+
346
+ #When method on ModelA:ID
347
+ When /^I "([^"]+)" for (.+):(.+)$/ do |requested_method_name, requested_model, default_identifier|
348
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
349
+ model_under_test.send(requested_method_name.underscore)
350
+ end
351
+
352
+ #Then Model:default_identifier should exist
353
+ Then /^(.+):(.+) should exist$/ do |requested_model, default_identifier|
354
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
355
+ model_under_test.should_not be_nil
356
+ end
357
+
358
+ Given /^no (\D+) should exist$/ do |requested_models|
359
+ model_klass = requested_model_to_model(requested_models)
360
+ model_klass.count.should == 0
361
+ end
362
+
363
+ #Then the following ModelAs should exist
364
+ #Works against actual models (instead of view)
365
+ #Verifies model count and each method in each row.
366
+ #Assumes:
367
+ # * Header = method name
368
+ # * the first column in each row is the default identifier for that row.
369
+ Then /^(?:these|the following) (.+) should exist:$/ do |requested_model, table|
370
+ # table is a Cucumber::Ast::Table
371
+ model_klass = requested_model_to_model(requested_model)
372
+
373
+ models_to_verify = requested_models(requested_model)
374
+ assert_models(table, models_to_verify)
375
+ end
376
+
377
+ Then /^there should be (\d*) (.*)$/ do |cnt, requested_model|
378
+ requested_model_to_model(requested_model).count.should == cnt.to_i
379
+ end
380
+
381
+ ###Predicates: Location:L1 should [not] be_reachable
382
+ ###Moved these to rspec. Left as example. Feel free to delete.
383
+ #Then /^(\D+):(.+) (should|should not) (be \D+)$/ do |requested_model, default_identifier, expectation, predicate_matcher|
384
+ # model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
385
+ # model_under_test.send(expectation.underscore, send(predicate_matcher.underscore))
386
+ #end
387
+
388
+ #Then Location:L1 should have the following Pings
389
+ #Works against actual models (instead of view)
390
+ #Verifies model count and each method in each row.
391
+ #Assumes:
392
+ # * Header = method name
393
+ # * the first column in each row is the default identifier for that row.
394
+ Then /^(\w+):(.+) should have (?:these|the following|this) (.+):$/ do |requested_model, default_identifier, association, table|
395
+ # table is a Cucumber::Ast::Table
396
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
397
+
398
+ associated_models = model_under_test.send(association.underscore)
399
+ assert_models(table, associated_models)
400
+ end
401
+
402
+ #Then ModelA should have 1 ModelB
403
+ Then /^(\w+):(.+) should have (\d+) (.+)$/ do |requested_model, default_identifier, association_count, association|
404
+ model_under_test = requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
405
+
406
+ associated_models = model_under_test.send(association.underscore)
407
+ associated_models.size.should == association_count.to_i
408
+ end
409
+
410
+
411
+ private
412
+
413
+ #Compare table values against the expected_models' methods
414
+ #Use '*' as a wild card.
415
+ #
416
+ #Assumes:
417
+ # * Header = method name
418
+ # * the first column in each row is the default identifier for that row.
419
+ def assert_models(table, *expected_models)
420
+ expected_models.flatten!
421
+ model_klass = expected_models.first && expected_models.first.class.base_class rescue expected_models.first.class #support non-AR models
422
+
423
+ map_table_columns!(table)
424
+ rows = map_table_headers(table).hashes
425
+ expected_models.count.should == rows.size
426
+
427
+ first_column_name = table.headers[0]
428
+
429
+ rows.each_with_index do |requested_params, row_index|
430
+ #Assume first column is unique identifier
431
+ #TODO: just use all columns as conditions.
432
+ default_identifier = requested_params[first_column_name]
433
+
434
+ #find the model for this row
435
+ model_under_test = expected_models.detect {|model| model.send(first_column_name).to_s == default_identifier }
436
+
437
+ #compare model with expectations
438
+ requested_params.each do |attribute_name, expected_value|
439
+ actual = model_under_test.send(attribute_name)
440
+ if actual.is_a?(ActiveRecord::Base)
441
+ #if AR model, compare against value of default_identifier_column
442
+ actual = actual.send(actual.class.default_identifier_column)
443
+ end
444
+
445
+ err_msg = "Expected ##{attribute_name} for '#{model_klass.name}:#{default_identifier}'\n\t to be: '#{expected_value}'\n\tbut was: '#{actual}'\n * Expectations: #{requested_params.inspect} \n * #{model_klass.name}:#{default_identifier}: #{model_under_test.inspect}.\n\n"
446
+ if expected_value =~ /[*]/ #has wild card
447
+ expected_value = expected_value.gsub('*', '.*')
448
+ actual.to_s.should match(expected_value), err_msg
449
+ else
450
+ actual.to_s.should eql(expected_value.to_s), err_msg
451
+ end
452
+
453
+ end
454
+ end
455
+ end
456
+
457
+ def requested_model_with_identifier_to_model_instance(requested_model, default_identifier)
458
+ model = requested_model_to_model(requested_model)
459
+ model_under_test = model.find_by_default_identifier!(default_identifier)
460
+ return model_under_test
461
+ rescue ActiveRecord::RecordNotFound
462
+ factory_name = model_to_factory_symbol(requested_model)
463
+ Factory.create(factory_name, model.default_identifier_column => default_identifier)
464
+ end
465
+
466
+ def assign_requested_model_associations(model_under_test, association_quantity, requested_association_name, array_of_requested_params = [])
467
+ model = model_under_test.class
468
+
469
+ association_model = requested_model_to_model(requested_association_name)
470
+ association_name = association_model.name.pluralize.underscore
471
+
472
+
473
+ #TODO: utilize associations in find
474
+ #convert {'location' => 'L1'}
475
+ # to {:location_id => 1}
476
+ # aka {:association.foreign_key => requested_model.id}
477
+
478
+ existing_objects = array_of_requested_params.collect do |conditions|
479
+ if conditions.keys.first == 'default_identifier'
480
+ #for CameraEvent find by (ImportFile).name
481
+ association_model.find_by_default_identifier(conditions.values.first)
482
+ else
483
+ association_model.find(:first, :conditions => conditions)
484
+ end
485
+ end
486
+
487
+ #assign to parent
488
+ model_under_test.send("#{association_name}=", existing_objects)
489
+
490
+ assert_equal association_quantity, model_under_test.send(association_name).size, "#{model.name} has incorrect # of #{association_name}"
491
+ end
492
+
493
+ #polymorphic associations are handled during 'assign_to_parent'
494
+ def create_requested_model_associations(model_under_test, association_quantity, requested_association_name, array_of_requested_params = [], default_params = {})
495
+ model = model_under_test.class
496
+
497
+ association_model = requested_model_to_model(requested_association_name)
498
+ association_name = model_to_association_method(requested_association_name)
499
+ association_factory_name = model_to_factory_symbol(requested_association_name)
500
+
501
+ parent_association = {}
502
+ parent = model.name.underscore
503
+
504
+ parent_association = {}
505
+ if association_model.instance_methods.include?(parent)
506
+ parent_association = {parent => model_under_test}
507
+ elsif association_model.instance_methods.include?(parent.pluralize)
508
+ parent_association = {parent.pluralize => [model_under_test]}
509
+ end
510
+
511
+ objects_to_associate = association_quantity.times.collect do |idx|
512
+
513
+ #parse requested params
514
+ converted_params = {}
515
+ unless array_of_requested_params.blank?
516
+ requested_params = array_of_requested_params[idx].dup
517
+ converted_params = requested_params_to_model_params(requested_params, association_model)
518
+ end
519
+
520
+ association_model_params = {}
521
+ association_model_params.merge!(parent_association)
522
+ association_model_params.merge!(default_params.merge(converted_params))
523
+ cleaned_params = {}
524
+ association_model_params.each {|key, value| cleaned_params[key] = (value.blank? ? nil : value) }
525
+ Factory.create(association_factory_name, cleaned_params)
526
+ end
527
+
528
+ #assign to parent
529
+ if association_name == association_name.singularize
530
+ model_under_test.send("#{association_name}=", objects_to_associate.first)
531
+
532
+ #TODO: why odes it perform the assignment (verified in db), but still return nil?
533
+ assert_not_nil model_under_test.send(association_name)
534
+ else
535
+ #append objects, do not assign array of objects.
536
+ objects_to_associate.each do |associated_object|
537
+ association = model_under_test.send("#{association_name}")
538
+ association.send('<<', associated_object) unless association.include?(associated_object)
539
+ end
540
+ # model_under_test.send("#{association_name}=", objects_to_associate)
541
+ scoped_association_name = model_to_association_method(requested_association_name, true)
542
+ association, scope = scoped_association_name.split('.')
543
+ associated_models = model_under_test.send(association)
544
+ if scope
545
+ associated_models = associated_models.send(scope)
546
+ end
547
+ assert_equal association_quantity, associated_models.size, "#{model.name} has incorrect # of #{scoped_association_name}"
548
+ end
549
+ end
550
+
551
+ def fill_in_required_fields(requested_model, rejected_fields = [])
552
+ model = requested_model_to_model(requested_model)
553
+ model_under_test = model.new(Factory.attributes_for(requested_model.underscore.to_sym))
554
+ testable_attributes = model_under_test.attributes.reject {|attribute_name, value| rejected_fields.include?(attribute_name) }
555
+
556
+ testable_attributes.each do |attribute_name, value|
557
+ if model_under_test.attribute_required?(attribute_name)
558
+ When "I fill in \"#{attribute_name.to_s.titleize}\" with \"#{value}\""
559
+ end
560
+ end
561
+ end
562
+
563
+ def map_table_columns!(table)
564
+ table.map_chronic_columns!
565
+ table.map_columns!(['size']) { |cell_value| eval(cell_value) if cell_value }
566
+ table.map_columns!(['trouble']) {|trouble_message| Factory.create(:trouble, :message => trouble_message)}
567
+ end
568
+
569
+ def map_table_header(header)
570
+ #TODO: associations should be underscore'd
571
+ # mapped_header = header.columnify
572
+ mapped_header = header
573
+ mapped_header.sub!('#', 'Number')
574
+ case header
575
+ when 'printer'
576
+ mapped_header = 'printer_prefix'
577
+ end
578
+ mapped_header
579
+ end
580
+
581
+ def map_table_headers(table)
582
+
583
+ returning(mappings = {}) do
584
+ table.headers.each do |header|
585
+ mappings[header] = map_table_header(header)
586
+ end
587
+ end
588
+
589
+ table.map_headers(mappings)
590
+ end
591
+
592
+ #converts model or model name to symbol for factory
593
+ #Examples:
594
+ # image --> :image
595
+ # Image --> :image
596
+ # Images --> :image
597
+ # Images(for scene A) --> :image_for_scene_a
598
+ #
599
+ def model_to_factory_symbol(model_or_name)
600
+ model_name =
601
+ case model_or_name
602
+ when /^(.+)[(](.+)[)]$/ #handle model(with associations), model(for scene A)
603
+ model_name = $1.singularize
604
+ factory_suffix = $2
605
+ "#{model_name}_#{factory_suffix}"
606
+ when String
607
+ model_or_name
608
+ else
609
+ model_or_name.name
610
+ end
611
+ model_name.singularize.underscore.to_sym
612
+ end
613
+
614
+ #converts model or model name to association method
615
+ #Examples:
616
+ # image --> .image
617
+ # Image --> .image
618
+ # Images --> .images
619
+ # Images(for scene A) --> .images.for_scene_a
620
+ #
621
+ def model_to_association_method(model_or_name, include_scope = false)
622
+ requested_association =
623
+ case model_or_name
624
+ when /^(.+)[(](.+)[)]$/ #handle model(with associations), i.e. Image(for scene A)
625
+ association = $1
626
+ scope = $2
627
+ include_scope ? "#{association}.#{scope}" : association
628
+ when String
629
+ model_or_name
630
+ else
631
+ model_or_name.name
632
+ end
633
+ requested_association.underscore
634
+ end
635
+
636
+ #Retrieves requested models
637
+ #
638
+ #examples:
639
+ # image --> Image.all
640
+ # Image --> Image.all
641
+ # Images --> Image.all
642
+ # Images(for scene A) --> Image.for_scene_a
643
+ # Images(active, for scene A) --> Image.active.for_scene_a
644
+ def requested_models(requested_model)
645
+ case requested_model
646
+ when /^(.+)[(](.+)[)]$/ #handle model(with associations), i.e. Image(for scene A)
647
+ base_model = $1.classify.constantize
648
+ scopes = $2.split(',')
649
+ models = base_model
650
+
651
+ scopes.each do |scope|
652
+ models = models.send(scope.strip)
653
+ end
654
+
655
+ models.all
656
+
657
+ when String #is name
658
+ requested_model.singularize.constantize.all
659
+ else
660
+ requested_model.all
661
+ end
662
+ end
663
+
664
+
665
+ def perform_activity(model, default_identifier, requested_activity)
666
+ model_under_test = model.find_by_default_identifier(default_identifier)
667
+ activity = model_under_test.send("do#{requested_activity.singularize}".underscore)
668
+ end
669
+
670
+ #TODO: extract concept for these
671
+ def requested_model_to_model(requested_model)
672
+ #move "cases" to mpr_model_steps.
673
+ case requested_model
674
+ when /^Site[s]?$/i, /^PhosphorylationSite[s]?$/i
675
+ return StySiteAbstractGene
676
+ when /^(.+)[(](.+)[)]$/ #handle model(with associations), model(for scene a)
677
+ return requested_model_to_model($1)
678
+ else
679
+ possible_model_name = requested_model.singularize.underscore.classify
680
+ #Note Ping class exists, so check for PingActivity first.
681
+ return "#{possible_model_name}Activity".constantize rescue nil
682
+ return possible_model_name.constantize rescue nil
683
+ end
684
+
685
+ raise "Requested Model (#{requested_model}, as #{possible_model_name}) is not supported."
686
+ end
687
+
688
+
689
+ def requested_params_to_model_params(requested_params, model)
690
+ converted_params = {}
691
+ #pull put associations
692
+ association_names = model.reflect_on_all_associations.collect &:name
693
+
694
+ mapped_params = {}
695
+ requested_params.each {|header, value| mapped_params[header.columnify] = value}
696
+
697
+ association_params = mapped_params.reject { |param_name, value| !association_names.include?(ModelSteps::Inflector.param_to_association_name(param_name)) }
698
+ association_params.each do |param_name, value|
699
+ next unless value
700
+
701
+ association_name = ModelSteps::Inflector.param_to_association_name(param_name).to_s
702
+
703
+ if value.include?(':') #is a model:unique_id
704
+ associated_model_class_name, default_identifier = value.split(':')
705
+ else
706
+ association = model.reflect_on_all_associations.detect {|each_association| each_association.name == ModelSteps::Inflector.param_to_association_name(param_name)}
707
+ associated_model_class_name = association.options[:class_name] || association_name.classify
708
+ default_identifier = value
709
+ end
710
+
711
+ associated_model = associated_model_class_name.constantize.find_by_default_identifier(default_identifier)
712
+
713
+ # TODO handle multiple associations
714
+ if /s$/ =~ association_name
715
+ converted_params[association_name] = [associated_model]
716
+ else
717
+ converted_params[association_name] = associated_model
718
+ end
719
+ end
720
+
721
+ model_ar_attr_params = mapped_params.reject {|param_name, value| !model.column_names.include?(param_name)}
722
+ model_attr_setter_params = mapped_params.reject {|param_name, value| !model.instance_methods.include?(param_name + '=') || association_params.keys.include?(param_name + '=')}
723
+
724
+
725
+ model_setter_params = mapped_params.reject {|param_name, value| !model_ar_attr_params.keys.include?(param_name) && !model_attr_setter_params.keys.include?(param_name) }
726
+
727
+ #TODO: pass date_column_names from class, instead of class?
728
+ model_params = model_setter_params#.parse_dates(model)
729
+
730
+ non_model_params = mapped_params.reject do |param_name, value|
731
+ model_setter_params.keys.include?(param_name) || association_params.keys.include?(param_name)
732
+ end
733
+
734
+ non_model_params.each do |param_name, value|
735
+ case param_name
736
+ when 'location_code'
737
+ location_association = model.new.is_a?(Activity) ? :toiler : :location
738
+ converted_params[location_association] = Location.find_by_location_code(value)
739
+ when 'success?'
740
+ success = non_model_params['success?'].to_bool
741
+ unless success
742
+ converted_params[:command_trouble] = CommandTrouble.new(:message => 'TESTING TROUBLE')
743
+ end
744
+ when 'trouble'
745
+ message = non_model_params['trouble']
746
+ converted_params[:trouble] = Trouble.new(:message => message) if message
747
+ else
748
+ #TODO:
749
+ raise "Header (#{param_name}) is not supported for #{model.name}."
750
+ end
751
+ end
752
+ model_params.merge(converted_params)
753
+ end
754
+
755
+ module ModelSteps
756
+ class Inflector
757
+ def self.param_to_association_name(param_name)
758
+ param_name.underscore.to_sym
759
+ end
760
+ end
761
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mattscilipoti-model_steps
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Scilipoti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-24 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: micronaut
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Model Steps for cucumber
36
+ email: matt@scilipoti.name
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - examples/example_helper.rb
52
+ - examples/mattscilipoti-model_steps_example.rb
53
+ - features/mattscilipoti-model_steps.feature
54
+ - features/step_definitions/mattscilipoti-model_steps_steps.rb
55
+ - features/support/env.rb
56
+ - lib/mattscilipoti-model_steps.rb
57
+ - lib/model_steps/step_definitions.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/mattscilipoti/mattscilipoti-model_steps
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.5
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Model Steps for cucumber
86
+ test_files:
87
+ - examples/example_helper.rb
88
+ - examples/mattscilipoti-model_steps_example.rb