ruby_odata 0.1.0 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +7 -2
  4. data/.simplecov +10 -0
  5. data/.travis.yml +10 -4
  6. data/.yardopts +6 -0
  7. data/CHANGELOG.md +157 -0
  8. data/Guardfile +14 -0
  9. data/LICENSE +20 -22
  10. data/README.md +289 -0
  11. data/Rakefile +19 -9
  12. data/features/basic_auth.feature +3 -2
  13. data/features/batch_request.feature +7 -6
  14. data/features/cassettes/basic_auth_protected_resource.yml +57 -0
  15. data/features/cassettes/batch_request_additions.yml +69 -0
  16. data/features/cassettes/batch_request_deletes.yml +69 -0
  17. data/features/cassettes/batch_request_updates.yml +69 -0
  18. data/features/cassettes/clean_database_for_testing.yml +46 -0
  19. data/features/cassettes/cucumber_tags/basic_auth.yml +297 -0
  20. data/features/cassettes/cucumber_tags/batch_request.yml +1459 -0
  21. data/features/cassettes/cucumber_tags/complex_types.yml +326 -0
  22. data/features/cassettes/cucumber_tags/error_handling.yml +64 -0
  23. data/features/cassettes/cucumber_tags/query_builder.yml +2025 -0
  24. data/features/cassettes/cucumber_tags/service.yml +234 -0
  25. data/features/cassettes/cucumber_tags/service_manage.yml +937 -0
  26. data/features/cassettes/cucumber_tags/service_methods.yml +647 -0
  27. data/features/cassettes/cucumber_tags/ssl.yml +203 -0
  28. data/features/cassettes/cucumber_tags/type_conversion.yml +337 -0
  29. data/features/cassettes/service_manage_additions.yml +65 -0
  30. data/features/cassettes/service_manage_deletions.yml +58 -0
  31. data/features/cassettes/service_manage_deletions_2.yml +58 -0
  32. data/features/cassettes/unsecured_metadata.yml +89 -0
  33. data/features/complex_types.feature +4 -3
  34. data/features/error_handling.feature +13 -0
  35. data/features/query_builder.feature +30 -9
  36. data/features/service.feature +4 -3
  37. data/features/service_manage.feature +6 -5
  38. data/features/service_methods.feature +3 -2
  39. data/features/ssl.feature +8 -8
  40. data/features/step_definitions/service_steps.rb +32 -74
  41. data/features/support/env.rb +6 -3
  42. data/features/support/hooks.rb +3 -2
  43. data/features/support/pickle.rb +29 -18
  44. data/features/support/vcr.rb +24 -0
  45. data/features/type_conversion.feature +16 -17
  46. data/gemfiles/Gemfile.ruby187 +6 -0
  47. data/lib/ruby_odata.rb +20 -18
  48. data/lib/ruby_odata/association.rb +7 -6
  49. data/lib/ruby_odata/class_builder.rb +31 -14
  50. data/lib/ruby_odata/exceptions.rb +11 -0
  51. data/lib/ruby_odata/helpers.rb +17 -0
  52. data/lib/ruby_odata/operation.rb +5 -6
  53. data/lib/ruby_odata/property_metadata.rb +9 -7
  54. data/lib/ruby_odata/query_builder.rb +127 -63
  55. data/lib/ruby_odata/service.rb +265 -147
  56. data/lib/ruby_odata/version.rb +3 -1
  57. data/ruby_odata.gemspec +22 -13
  58. data/spec/fixtures/error_without_message.xml +5 -0
  59. data/spec/fixtures/int64_ids/edmx_boat_service.xml +19 -0
  60. data/spec/fixtures/int64_ids/edmx_car_service.xml +21 -0
  61. data/spec/fixtures/int64_ids/result_boats.xml +26 -0
  62. data/spec/fixtures/int64_ids/result_cars.xml +28 -0
  63. data/spec/fixtures/ms_system_center/edmx_ms_system_center.xml +1645 -0
  64. data/spec/fixtures/ms_system_center/edmx_ms_system_center_v2.xml +2120 -0
  65. data/spec/fixtures/ms_system_center/hardware_profiles.xml +61 -0
  66. data/spec/fixtures/ms_system_center/virtual_machines.xml +175 -0
  67. data/spec/fixtures/ms_system_center/vm_templates.xml +1193 -0
  68. data/spec/fixtures/nested_expands/edmx_northwind.xml +557 -0
  69. data/spec/fixtures/nested_expands/northwind_products_category_expands.xml +774 -0
  70. data/spec/fixtures/sample_service/result_select_categories_expand.xml +268 -0
  71. data/spec/fixtures/sample_service/result_select_categories_no_property.xml +6 -0
  72. data/spec/fixtures/sample_service/result_select_categories_travsing_no_expand.xml +6 -0
  73. data/spec/fixtures/sample_service/result_select_products_name_price.xml +92 -0
  74. data/spec/property_metadata_spec.rb +10 -10
  75. data/spec/query_builder_spec.rb +153 -14
  76. data/spec/revised_service_spec.rb +111 -6
  77. data/spec/service_spec.rb +389 -85
  78. data/spec/spec_helper.rb +3 -0
  79. data/spec/support/sample_service_matcher.rb +15 -0
  80. data/test/RubyODataService/RubyODataService/App_Data/.gitkeep +0 -0
  81. data/test/blueprints.rb +15 -9
  82. data/test/usage_samples/querying.rb +5 -1
  83. data/test/usage_samples/sample_data.rb +1 -3
  84. metadata +276 -76
  85. data/CHANGELOG.rdoc +0 -88
  86. data/README.rdoc +0 -259
@@ -1,3 +1,4 @@
1
+ @service_methods
1
2
  Feature: Service methods
2
3
  In order to use a WCF Data Service more efficiently
3
4
  As a consumer
@@ -5,9 +6,9 @@ Feature: Service methods
5
6
 
6
7
  Background:
7
8
  Given a HTTP ODataService exists
8
- And blueprints exist for the service
9
+ And blueprints exist for the service
10
+
9
11
 
10
-
11
12
  Scenario: A custom web get (no parameters) that returns an entity
12
13
  Given a category exists
13
14
  And I call the service method "EntityCategoryWebGet"
@@ -1,13 +1,14 @@
1
+ @ssl
1
2
  Feature: Service Should Access Basic Resources via SSL using a self-signed certificate
2
-
3
- Background:
4
- Given a HTTPS BasicAuth ODataService exists using self-signed certificate and username "admin" and password "passwd"
5
- And blueprints exist for the service
6
3
 
7
4
  Scenario: Service should respond to valid collections
5
+ Given a HTTPS BasicAuth ODataService exists using self-signed certificate and username "admin" and password "passwd"
6
+ And blueprints exist for the service
8
7
  Then I should be able to call "Products" on the service
9
8
 
10
9
  Scenario: Entity should fill values on protected resource
10
+ Given a HTTPS BasicAuth ODataService exists using self-signed certificate and username "admin" and password "passwd"
11
+ And blueprints exist for the service
11
12
  Given I call "AddToCategories" on the service with a new "Category" object with Name: "Auth Test Category"
12
13
  And I save changes
13
14
  And I call "Categories" on the service with args: "1"
@@ -15,7 +16,6 @@ Scenario: Entity should fill values on protected resource
15
16
  Then the method "Id" on the first result should equal: "1"
16
17
  And the method "Name" on the first result should equal: "Auth Test Category"
17
18
 
18
- Scenario: Should get SSL failure if SSL used with self-signed certificate and not passing "false" as :verify_ssl option
19
- Given a HTTPS BasicAuth ODataService exists it should throw an exception with message containing "SSL Verification failed"
20
-
21
-
19
+ # TODO: Commented this scenario out because it requires the server to be online in order for the tests to run. Would like to move this to a mocked test that would accomplish the same thing...
20
+ # Scenario: Should get SSL failure if SSL used with self-signed certificate and not passing "false" as :verify_ssl option
21
+ # Given a HTTPS BasicAuth ODataService exists it should throw an exception with message containing "SSL Verification failed"
@@ -2,47 +2,33 @@ STANDARD_URL = "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/RubyOData.
2
2
  BASICAUTH_URL = "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/BasicAuth/RubyOData.svc"
3
3
  HTTPS_BASICAUTH_URL = "https://#{WEBSERVER}:#{HTTPS_PORT_NUMBER}/SampleService/BasicAuth/RubyOData.svc"
4
4
 
5
- When /^(.*) first (last query )?result('s)?(.*)$/ do |pre, last_query, apos, post|
6
- step = "#{pre} #{last_query}result#{apos}#{post}"
5
+ When /^(.*) first (last query )?result(\'s)?(.*)$/ do |pre, last_query, apos, post|
6
+ the_step = "#{pre} #{last_query}result#{apos}#{post}"
7
7
  first_result
8
- When step
8
+ step the_step
9
9
  end
10
10
 
11
11
  When /^(.*) first (last )?save result(.*)$/ do |pre, last, post|
12
- step = "#{pre} #{last}save result#{post}"
12
+ the_step = "#{pre} #{last}save result#{post}"
13
13
  first_save
14
- When step
14
+ step the_step
15
15
  end
16
16
 
17
17
  Given /^a HTTP ODataService exists$/ do
18
- @service = OData::Service.new(STANDARD_URL)
19
- end
20
-
21
- Given /^a HTTP BasicAuth ODataService exists$/ do
22
- @service = OData::Service.new(BASICAUTH_URL)
23
- end
24
-
25
- Given /^a HTTPS BasicAuth ODataService exists$/ do
26
- @service = OData::Service.new(HTTPS_BASICAUTH_URL)
18
+ VCR.use_cassette("unsecured_metadata") do
19
+ @service = OData::Service.new(STANDARD_URL)
20
+ end
27
21
  end
28
22
 
29
23
  Given /^a HTTP BasicAuth ODataService exists using username "([^\"]*)" and password "([^\"]*)"$/ do |username, password|
30
24
  @service = OData::Service.new(BASICAUTH_URL, { :username => username, :password => password })
31
25
  end
32
26
 
33
- Given /^a HTTP BasicAuth ODataService exists using username "([^\"]*)" and password "([^\"]*)" it should throw an exception with message "([^\"]*)"$/ do |username, password, msg|
27
+ Given /^a HTTP BasicAuth ODataService exists using username "([^\"]*)" and password "([^\"]*)" it should throw an exception with message "([^\"]*)"$/ do |username, password, msg|
34
28
  lambda { @service = OData::Service.new(BASICAUTH_URL, { :username => username, :password => password }) }.should raise_error(msg)
35
29
  end
36
30
 
37
- Given /^a HTTP BasicAuth ODataService exists it should throw an exception with message containing "([^\"]*)"$/ do |msg|
38
- lambda { @service = OData::Service.new(BASICAUTH_URL) }.should raise_error(/#{msg}.*/)
39
- end
40
-
41
- Given /^a HTTPS BasicAuth ODataService exists it should throw an exception with message containing "([^"]*)"$/ do |msg|
42
- lambda { @service = OData::Service.new(HTTPS_BASICAUTH_URL) }.should raise_error(/#{msg}.*/)
43
- end
44
-
45
- Given /^a HTTP BasicAuth ODataService exists it should throw an exception with message "([^\"]*)"$/ do |msg|
31
+ Given /^a HTTP BasicAuth ODataService exists it should throw an exception with message "([^\"]*)"$/ do |msg|
46
32
  lambda { @service = OData::Service.new(BASICAUTH_URL) }.should raise_error(msg)
47
33
  end
48
34
 
@@ -54,10 +40,6 @@ When /^I call "([^\"]*)" on the service$/ do |method|
54
40
  @service_query = @service.send(method)
55
41
  end
56
42
 
57
- Then /^the result should be "([^\"]*)"$/ do |result|
58
- @service_result.should eq result
59
- end
60
-
61
43
  Then /^the integer result should be ([^\"]*)$/ do |result|
62
44
  @service_result.should eq result.to_i
63
45
  end
@@ -122,18 +104,22 @@ When /^I ask for the top (\d+)$/ do |top|
122
104
  @service_query.top(top)
123
105
  end
124
106
 
107
+ When /^I ask for the count$/ do
108
+ @service_query.count
109
+ end
110
+
125
111
  When /^I ask for the links for "([^\"]*)"$/ do |nav_prop|
126
112
  @service_query.links(nav_prop)
127
113
  end
128
114
 
129
115
  Then /^the method "([^\"]*)" on the result should be of type "([^\"]*)"$/ do |method, type|
130
- result = @service_result.send(method.to_sym)
116
+ result = @service_result.send(method.to_sym)
131
117
  result.class.to_s.should eq type
132
118
  end
133
119
 
134
120
  Given /^I call "([^\"]*)" on the service with a new "([^\"]*)" object(?: with (.*))?$/ do |method, object, fields|
135
121
  fields_hash = parse_fields_string(fields)
136
-
122
+
137
123
  obj = object.constantize.send(:make, fields_hash)
138
124
  @service.send(method.to_sym, obj)
139
125
  end
@@ -172,6 +158,10 @@ Given /^I call "([^\"]*)" on the service with a new "([^\"]*)" object it should
172
158
  lambda { @service.send(method.to_sym, obj) }.should raise_error(msg)
173
159
  end
174
160
 
161
+ When /^I save changes it should throw an exception with message containing "([^"]*)"$/ do |msg|
162
+ lambda { @service.save_changes }.should raise_error(/#{msg}.*/)
163
+ end
164
+
175
165
  Then /^no "([^\"]*)" should exist$/ do |collection|
176
166
  @service.send(collection)
177
167
  results = @service.execute
@@ -180,19 +170,19 @@ end
180
170
 
181
171
  Then /^the primitive results should be:$/ do |table|
182
172
  # table is a Cucumber::Ast::Table
183
- values = table.hashes
173
+ values = table.hashes
184
174
  result_table = Cucumber::Ast::Table.new(values)
185
175
  table.diff!(result_table)
186
176
  end
187
177
 
188
178
  Then /^the result should be:$/ do |table|
189
179
  # table is a Cucumber::Ast::Table
190
-
180
+
191
181
  fields = table.hashes[0].keys
192
-
182
+
193
183
  # Build an array of hashes so that we can compare tables
194
184
  results = []
195
-
185
+
196
186
  @service_result.each do |result|
197
187
  obj_hash = Hash.new
198
188
  fields.each do |field|
@@ -200,31 +190,10 @@ Then /^the result should be:$/ do |table|
200
190
  end
201
191
  results << obj_hash
202
192
  end
203
-
204
- result_table = Cucumber::Ast::Table.new(results)
205
-
206
- table.diff!(result_table)
207
- end
208
193
 
209
- Then /^the save result should be:$/ do |table|
210
- # table is a Cucumber::Ast::Table
211
-
212
- fields = table.hashes[0].keys
213
-
214
- # Build an array of hashes so that we can compare tables
215
- results = []
216
-
217
- @saved_result.each do |result|
218
- obj_hash = Hash.new
219
- fields.each do |field|
220
- obj_hash[field] = result.send(field)
221
- end
222
- results << obj_hash
223
- end
224
-
225
194
  result_table = Cucumber::Ast::Table.new(results)
226
-
227
- table.diff!(result_table)
195
+
196
+ table.diff!(result_table)
228
197
  end
229
198
 
230
199
  Then /^a class named "([^\"]*)" should exist$/ do |klass_name|
@@ -251,14 +220,6 @@ When /^I set "([^\"]*)" on the result's method "([^\"]*)" to "([^\"]*)"$/ do |pr
251
220
  end
252
221
 
253
222
  # Type tests
254
- Then /^the "([^\"]*)" method should return a (.*)/ do |method_name, type|
255
- methods = method_name.split '.'
256
- if methods.length == 1
257
- @service_result.send(method_name).class.to_s.should eq type
258
- else
259
- @service_result.send(methods[0]).send(methods[1]).class.to_s.should eq type
260
- end
261
- end
262
223
  Then /^the "([^\"]*)" method on the object should return a (.*)/ do |method_name, type|
263
224
  methods = method_name.split '.'
264
225
  if methods.length == 1
@@ -274,16 +235,9 @@ end
274
235
 
275
236
  Then /^the new query result's time "([^\"]*)" should equal the saved query result$/ do |method_name|
276
237
  methods = method_name.split '.'
277
- if methods.length == 1
278
- @service_result.send(method_name).xmlschema(3).should eq @stored_query_result.send(method_name).xmlschema(3)
279
- else
280
- @service_result.send(methods[0]).send(methods[1]).xmlschema(3).should eq @stored_query_result.send(methods[0]).send(methods[1]).xmlschema(3)
281
- end
238
+ @service_result.send(methods[0]).send(methods[1]).xmlschema(3).should eq @stored_query_result.send(methods[0]).send(methods[1]).xmlschema(3)
282
239
  end
283
240
 
284
- Then /^show me the results$/ do
285
- puts @service_result
286
- end
287
241
 
288
242
  Then /^the result count should be (\d+)$/ do |expected_count|
289
243
  @service_result.count.should eq expected_count.to_i
@@ -299,4 +253,8 @@ Given /^I call the service method "([^"]*)"(?: with (.*))?$/ do |method, args|
299
253
  else
300
254
  @service_result = @service.send(method)
301
255
  end
302
- end
256
+ end
257
+
258
+ When /^(.*) within a cassette named "([^"]*)"$/ do |the_step, cassette_name|
259
+ VCR.use_cassette(cassette_name) { step the_step }
260
+ end
@@ -1,6 +1,9 @@
1
1
  lib = File.expand_path(File.join(File.dirname(__FILE__), "../..", "lib"))
2
2
 
3
3
  require lib + '/ruby_odata'
4
- require 'machinist/object'
5
- require 'sham'
6
- require 'faker'
4
+ require 'machinist'
5
+
6
+ require 'simplecov'
7
+ # require 'coveralls'
8
+
9
+ # Coveralls.wear_merged!
@@ -1,4 +1,5 @@
1
1
  Before do
2
- Sham.reset
3
- RestClient.post "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/RubyOData.svc/CleanDatabaseForTesting", {}
2
+ VCR.use_cassette("clean_database_for_testing") do
3
+ RestClient.post "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/RubyOData.svc/CleanDatabaseForTesting", {}
4
+ end
4
5
  end
@@ -1,23 +1,28 @@
1
- require 'pickle/world'
2
- require File.expand_path('../../../lib/ruby_odata', __FILE__)
1
+ require "pickle/world"
2
+ require "vcr"
3
+ require File.expand_path("../../../lib/ruby_odata", __FILE__)
3
4
 
4
5
  module OData
5
-
6
6
  module PickleAdapter
7
7
  include Pickle::Adapter::Base
8
-
9
- @@service = OData::Service.new "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/RubyOData.svc"
10
-
8
+ @@service = nil
9
+ def self.get_service
10
+ return @@service if @@service
11
+ VCR.use_cassette("unsecured_metadata") do
12
+ return OData::Service.new "http://#{WEBSERVER}:#{HTTP_PORT_NUMBER}/SampleService/RubyOData.svc"
13
+ end
14
+ end
15
+
11
16
  # Do not consider these to be part of the class list
12
17
  def self.except_classes
13
18
  @@except_classes ||= []
14
19
  end
15
-
20
+
16
21
  # Gets a list of the available models for this adapter
17
22
  def self.model_classes
18
- @@service.classes.values
23
+ self.get_service.classes.values
19
24
  end
20
-
25
+
21
26
  # get a list of column names for a given class
22
27
  def self.column_names(klass)
23
28
  klass.properties.keys
@@ -26,7 +31,9 @@ module OData
26
31
  # Get an instance by id of the model
27
32
  def self.get_model(klass, id, expand = false)
28
33
  collection = klass.to_s.split('::').last.pluralize
29
- query = @@service.send collection, id
34
+ service = self.get_service
35
+
36
+ query = service.send collection, id
30
37
 
31
38
  if expand then
32
39
  # Expand all navigation properties
@@ -38,24 +45,28 @@ module OData
38
45
  end
39
46
  end
40
47
 
41
- @@service.execute.first
48
+ service.execute.first
42
49
  end
43
50
 
44
51
  # Find the first instance matching conditions
45
52
  def self.find_first_model(klass, conditions)
46
53
  collection = klass.to_s.split('::').last.pluralize
47
- q = @@service.send collection
54
+ service = self.get_service
55
+
56
+ q = service.send collection
48
57
  q.filter(conditions)
49
58
  q.take(1)
50
- @@service.execute.first
59
+ service.execute.first
51
60
  end
52
61
 
53
62
  # Find all models matching conditions
54
63
  def self.find_all_models(klass, conditions)
55
64
  collection = klass.to_s.split('::').last.pluralize
56
- q = @@service.send collection
65
+ service = self.get_service
66
+
67
+ q = service.send collection
57
68
  q.filter(conditions)
58
- @@service.execute
69
+ service.execute
59
70
  end
60
71
 
61
72
  # Create a model using attributes
@@ -63,10 +74,10 @@ module OData
63
74
  instance = klass.send :make, attributes
64
75
 
65
76
  collection = klass.to_s.split('::').last.pluralize
66
- @@service.send "AddTo#{collection}", instance
67
- @@service.save_changes.first
77
+ service = self.get_service
78
+ service.send "AddTo#{collection}", instance
79
+ service.save_changes.first
68
80
  end
69
-
70
81
  end
71
82
  end
72
83
 
@@ -0,0 +1,24 @@
1
+ require "vcr"
2
+ require File.expand_path("../../../spec/support/sample_service_matcher", __FILE__)
3
+
4
+ VCR.configure do |c|
5
+ c.hook_into :webmock
6
+ c.cassette_library_dir = "features/cassettes"
7
+ c.default_cassette_options = {
8
+ :record => :none,
9
+ :match_requests_on => [:method, OData::Support::SampleServiceMatcher]
10
+ }
11
+ end
12
+
13
+ VCR.cucumber_tags do |t|
14
+ t.tags "@basic_auth",
15
+ "@batch_request",
16
+ "@complex_types",
17
+ "@error_handling",
18
+ "@query_builder",
19
+ "@service",
20
+ "@service_manage",
21
+ "@service_methods",
22
+ "@ssl",
23
+ "@type_conversion"
24
+ end
@@ -1,6 +1,7 @@
1
+ @type_conversion
1
2
  Feature: Type conversion
2
3
  In order to accurately perform operations
3
- As a user of the API
4
+ As a user of the API
4
5
  I want types returned to be accurately represented
5
6
 
6
7
  Background:
@@ -9,29 +10,29 @@ Background:
9
10
 
10
11
  Scenario: Integers should be Fixnums
11
12
  Given I call "AddToProducts" on the service with a new "Product" object
12
- And I save changes
13
+ And I save changes
13
14
  When I call "Products" on the service
14
15
  And I run the query
15
- Then the "Id" method on the object should return a Fixnum
16
-
16
+ Then the "Id" method on the object should return a Fixnum
17
+
17
18
  Scenario: Decimals should be BigDecimals
18
- Given I call "AddToProducts" on the service with a new "Product" object
19
+ Given I call "AddToProducts" on the service with a new "Product" object
19
20
  And I save changes
20
- When I call "Products" on the service
21
+ When I call "Products" on the service
21
22
  And I run the query
22
23
  Then the "Price" method on the object should return a BigDecimal
23
24
 
24
25
  Scenario: DateTimes should be Times
25
- Given I call "AddToProducts" on the service with a new "Product" object
26
+ Given I call "AddToProducts" on the service with a new "Product" object
26
27
  And I save changes
27
- When I call "Products" on the service
28
+ When I call "Products" on the service
28
29
  And I run the query
29
- Then the "AuditFields.CreateDate" method on the object should return a Time
30
-
30
+ Then the "AuditFields.CreateDate" method on the object should return a Time
31
+
31
32
  Scenario: Verify that DateTimes don't change if not modified on an update
32
- Given I call "AddToProducts" on the service with a new "Product" object with Name: "Test Product"
33
- When I save changes
34
- And I call "Products" on the service with args: "1"
33
+ Given I call "AddToProducts" on the service with a new "Product" object with Name: "Test Product"
34
+ When I save changes
35
+ And I call "Products" on the service with args: "1"
35
36
  And I run the query
36
37
  Then I store the first last query result for comparison
37
38
  When I set "Name" on the first result to "Changed Test Product"
@@ -44,10 +45,8 @@ Scenario: Verify that DateTimes don't change if not modified on an update
44
45
  Then the new query first result's time "AuditFields.CreateDate" should equal the saved query result
45
46
 
46
47
  Scenario: DateTimes should be able to be null
47
- Given I call "AddToProducts" on the service with a new "Product" object
48
+ Given I call "AddToProducts" on the service with a new "Product" object
48
49
  And I save changes
49
- When I call "Products" on the service
50
+ When I call "Products" on the service
50
51
  And I run the query
51
52
  Then the "DiscontinuedDate" method on the object should return a NilClass
52
-
53
-