intermodal 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/LICENSE +1 -1
- data/README +6 -4
- data/Rakefile +6 -0
- data/intermodal.gemspec +51 -0
- data/lib/generators/intermodal_generator.rb +8 -0
- data/lib/intermodal.rb +122 -0
- data/lib/intermodal/api.rb +246 -0
- data/lib/intermodal/api/configuration.rb +40 -0
- data/lib/intermodal/api/railties.rb +21 -0
- data/lib/intermodal/concerns/acceptors/named_resource.rb +12 -0
- data/lib/intermodal/concerns/acceptors/resource.rb +12 -0
- data/lib/intermodal/concerns/controllers/accountability.rb +17 -0
- data/lib/intermodal/concerns/controllers/anonymous.rb +24 -0
- data/lib/intermodal/concerns/controllers/authenticatable.rb +24 -0
- data/lib/intermodal/concerns/controllers/paginated_collection.rb +25 -0
- data/lib/intermodal/concerns/controllers/presentation.rb +17 -0
- data/lib/intermodal/concerns/controllers/resource.rb +51 -0
- data/lib/intermodal/concerns/controllers/resource_linking.rb +58 -0
- data/lib/intermodal/concerns/let.rb +21 -0
- data/lib/intermodal/concerns/models/access_credential.rb +28 -0
- data/lib/intermodal/concerns/models/account.rb +16 -0
- data/lib/intermodal/concerns/models/accountability.rb +47 -0
- data/lib/intermodal/concerns/models/db_access_token.rb +29 -0
- data/lib/intermodal/concerns/models/has_parent_resource.rb +45 -0
- data/lib/intermodal/concerns/models/presentation.rb +25 -0
- data/lib/intermodal/concerns/models/redis_access_token.rb +60 -0
- data/lib/intermodal/concerns/models/resource_linking.rb +126 -0
- data/lib/intermodal/concerns/models/sanitize_html.rb +35 -0
- data/lib/intermodal/concerns/presenters/named_resource.rb +12 -0
- data/lib/intermodal/concerns/presenters/resource.rb +14 -0
- data/lib/intermodal/concerns/rails/rails_3_stack.rb +42 -0
- data/lib/intermodal/concerns/rails/rails_4_stack.rb +17 -0
- data/lib/intermodal/concerns/rails/use_warden.rb +21 -0
- data/lib/intermodal/config.rb +15 -0
- data/lib/intermodal/configuration.rb +11 -0
- data/lib/intermodal/controllers/api_controller.rb +26 -0
- data/lib/intermodal/controllers/linking_resource_controller.rb +8 -0
- data/lib/intermodal/controllers/nested_resource_controller.rb +18 -0
- data/lib/intermodal/controllers/resource_controller.rb +11 -0
- data/lib/intermodal/dsl/controllers.rb +125 -0
- data/lib/intermodal/dsl/mapping.rb +79 -0
- data/lib/intermodal/dsl/presentation_helpers.rb +107 -0
- data/lib/intermodal/mapping/acceptor.rb +2 -2
- data/lib/intermodal/mapping/mapper.rb +39 -13
- data/lib/intermodal/mapping/presenter.rb +12 -6
- data/lib/intermodal/proxies/linking_resources.rb +58 -0
- data/lib/intermodal/proxies/will_paginate.rb +85 -0
- data/lib/intermodal/rack/auth.rb +29 -0
- data/lib/intermodal/rack/dummy_store.rb +24 -0
- data/lib/intermodal/rack/rescue.rb +82 -0
- data/lib/intermodal/responders/linking_resource_responder.rb +21 -0
- data/lib/intermodal/responders/resource_responder.rb +64 -0
- data/lib/intermodal/rspec/acceptors.rb +79 -0
- data/lib/intermodal/rspec/models/accountability.rb +114 -0
- data/lib/intermodal/rspec/models/has_parent_resource.rb +132 -0
- data/lib/intermodal/rspec/models/resource_linking.rb +234 -0
- data/lib/intermodal/rspec/models/sanitization.rb +84 -0
- data/lib/intermodal/rspec/presenters.rb +92 -0
- data/lib/intermodal/rspec/requests/authenticated_requests.rb +17 -0
- data/lib/intermodal/rspec/requests/linked_resources.rb +180 -0
- data/lib/intermodal/rspec/requests/paginated_collection.rb +60 -0
- data/lib/intermodal/rspec/requests/rack.rb +142 -0
- data/lib/intermodal/rspec/requests/request_validations.rb +36 -0
- data/lib/intermodal/rspec/requests/resources.rb +275 -0
- data/lib/intermodal/rspec/requests/rfc2616_status_codes.rb +51 -0
- data/lib/intermodal/rspec/validators.rb +86 -0
- data/lib/intermodal/validators/account_validator.rb +27 -0
- data/lib/intermodal/validators/different_account_validator.rb +27 -0
- data/lib/intermodal/version.rb +3 -0
- data/spec/mapping/acceptors_spec.rb +142 -0
- data/spec/mapping/presenters_spec.rb +186 -0
- data/spec/models/accountability_spec.rb +13 -0
- data/spec/models/has_parent_resource_spec.rb +18 -0
- data/spec/models/resource_linking_spec.rb +21 -0
- data/spec/proxies/will_paginate_spec.rb +163 -0
- data/spec/rack/auth_spec.rb +51 -0
- data/spec/requests/linked_resources.rb +37 -0
- data/spec/requests/nested_resources_spec.rb +54 -0
- data/spec/requests/resources_spec.rb +50 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/api.rb +50 -0
- data/spec/support/app/class_builder.rb +41 -0
- data/spec/support/app/db/adapter_helper.rb +53 -0
- data/spec/support/app/db/authentication_schema_helper.rb +62 -0
- data/spec/support/app/db/migration_helper.rb +44 -0
- data/spec/support/app/schema.rb +101 -0
- data/spec/support/application.rb +23 -0
- data/spec/support/blueprints.rb +41 -0
- data/spec/support/epiphyte.rb +29 -0
- metadata +393 -52
- data/lib/intermodal/base.rb +0 -13
- data/lib/intermodal/declare_controllers.rb +0 -102
- data/lib/intermodal/mapping.rb +0 -4
- data/lib/intermodal/mapping/dsl.rb +0 -76
@@ -0,0 +1,36 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module RSpec
|
3
|
+
module RequestValidations
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
# This macro expects the following to be defined:
|
8
|
+
# let(:valid_payload) { request_payload that would succeed }
|
9
|
+
#
|
10
|
+
# You then pass a override hash and the expected response code,
|
11
|
+
# usually a 400 Bad Request or 422 Unprocessible Entity
|
12
|
+
#
|
13
|
+
# In overrides, if you pass a callable object such as a lambda or a proc,
|
14
|
+
# then it will be called at test time before merging into the request payload.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# overrides: { apple: -> { tree.apples.sample } }
|
19
|
+
def expect_request_invalid(message, opts={}, &additional_examples)
|
20
|
+
_status = opts[:status] || 422
|
21
|
+
_overrides = opts[:overrides] or raise 'Must pass overrides: parameter'
|
22
|
+
|
23
|
+
context "when #{message}" do
|
24
|
+
let(:request_payload) { attributes.merge(overrides) }
|
25
|
+
let(:overrides) { Hash[_overrides.map(&eval_hash)] }
|
26
|
+
let(:eval_hash) { ->(x) { [x[0].to_s, maybe_call.(x[1]) ] } }
|
27
|
+
let(:maybe_call) { ->(x) { x.respond_to?(:call) ? x.call : x } }
|
28
|
+
|
29
|
+
expects_status _status
|
30
|
+
instance_eval(&additional_examples) if additional_examples
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module RSpec
|
3
|
+
module Resources
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Intermodal::RSpec::Rack
|
8
|
+
include Intermodal::RSpec::AuthenticatedRequests
|
9
|
+
include Intermodal::RSpec::PaginatedCollection
|
10
|
+
|
11
|
+
let(:api) { raise "Must define let(:api)" }
|
12
|
+
|
13
|
+
let(:resource_collection_name) { resource_name.pluralize }
|
14
|
+
let(:model) { resource_name.camelize.constantize }
|
15
|
+
let(:parent_models) { parent_names.map { |p| p.to_s.singularize.camelize.constantize } }
|
16
|
+
|
17
|
+
let(:model_collection) do
|
18
|
+
3.times { model.make!(model_factory_options) }
|
19
|
+
(model_parent ? model.by_parent(model_parent) : model.all )
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:model_resource) { model.make!(model_factory_options) }
|
23
|
+
let(:model_factory_options) { (model_parent ? { parent_names.last.to_s.singularize => model_parent } : {} ) }
|
24
|
+
let(:model_parents) { parent_models.map { |p| p.make! } }
|
25
|
+
let(:model_parent) { model_parents.last }
|
26
|
+
let(:presenter) { api.presenter_for model }
|
27
|
+
|
28
|
+
let(:collection) { model_collection }
|
29
|
+
let(:paginated_collection) { model_collection.paginate(:page => page, :per_page => per_page) }
|
30
|
+
let(:presented_collection) { parser.decode(paginated_collection.send("to_#{format}", :presenter => presenter, :root => collection_element_name, :scope => presenter_scope_for_index)) }
|
31
|
+
let(:page) { 1 }
|
32
|
+
let(:per_page) { api.default_per_page }
|
33
|
+
|
34
|
+
let(:resource_element_name) { model.name.demodulize.underscore }
|
35
|
+
let(:collection_element_name) { resource_element_name.pluralize }
|
36
|
+
let(:expected_resource) { model.find(model_resource.id) }
|
37
|
+
let(:persisted_resource_id) { body['id'] }
|
38
|
+
let(:resource_after_create) { model.find(persisted_resource_id) }
|
39
|
+
let(:resource_after_update) { model.find(resource_id) }
|
40
|
+
let(:resource_after_destroy) { resource_after_update }
|
41
|
+
let(:resource) { parser.decode(expected_resource.send("to_#{format}", :presenter => presenter, :scope => presenter_scope)) }
|
42
|
+
let(:presenter_scope) { nil }
|
43
|
+
let(:presenter_scope_for_index) { presenter_scope }
|
44
|
+
let(:resource_id) { resource['id'] }
|
45
|
+
let(:parent_ids) { parent_names.zip(model_parents.map { |m| m.id }) }
|
46
|
+
|
47
|
+
let(:namespace) { nil }
|
48
|
+
let(:namespace_path) { ( namespace ? "/#{namespace}" : nil ) }
|
49
|
+
let(:parent_path) { parent_ids.map { |p, p_id| "/#{p}/#{p_id}" }.join }
|
50
|
+
let(:collection_url) { [ namespace_path, parent_path, "/#{resource_collection_name}.#{format}" ].join }
|
51
|
+
let(:resource_url) { [ namespace_path, parent_path, "/#{resource_collection_name}/#{resource_id}.#{format}" ].join }
|
52
|
+
|
53
|
+
let(:request_json_payload) { request_payload.to_json }
|
54
|
+
let(:request_xml_payload) { request_payload.to_xml(:root => resource_element_name) }
|
55
|
+
|
56
|
+
let(:malformed_json_payload) { '{ "bad": [ "data": ] }' }
|
57
|
+
let(:malformed_xml_payload) { '<bad><data></bad></data>' }
|
58
|
+
|
59
|
+
# DELETE specs expect record to be deleted.
|
60
|
+
# Override this if you are using something such as ActiveResource:
|
61
|
+
# let(:record_not_found_error) { ActiveResource::ResourceNotFound }
|
62
|
+
let(:record_not_found_error) { ActiveRecord::RecordNotFound }
|
63
|
+
|
64
|
+
# Some of the test examples assume that the database is blank.
|
65
|
+
# For example, index tests require injecting 3 resources and expects
|
66
|
+
# the index endpoint to return exactly 3 resources.
|
67
|
+
let(:reset_datastore!) { } # Do nothing by default
|
68
|
+
end
|
69
|
+
|
70
|
+
module ClassMethods
|
71
|
+
def given_create_attributes(values = {})
|
72
|
+
let(:valid_create_attributes) { values }
|
73
|
+
end
|
74
|
+
|
75
|
+
def given_update_attributes(values = {})
|
76
|
+
let(:valid_update_attributes) { values }
|
77
|
+
end
|
78
|
+
|
79
|
+
def metadata_for_resources(resource_name, options = {})
|
80
|
+
if resource_name.is_a?(Array)
|
81
|
+
parents = resource_name
|
82
|
+
resource_name = parents.pop
|
83
|
+
end
|
84
|
+
resource_name = resource_name.to_s if resource_name.is_a?(Symbol)
|
85
|
+
|
86
|
+
{ :formats => [ :json ],
|
87
|
+
:resource_name => resource_name,
|
88
|
+
:model_name => resource_name.singularize,
|
89
|
+
:encoding => 'utf-8',
|
90
|
+
:parents => parents || [],
|
91
|
+
:namespace_path => ( options[:namespace] ? "/#{options[:namespace]}" : nil )
|
92
|
+
}.merge(options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def metadata_for_formatted_resource(format, options = {})
|
96
|
+
{ :format => format,
|
97
|
+
:collection_url => collection_url_for_resource(options[:namespace_path], options[:parents], options[:resource_name], format),
|
98
|
+
:resource_url => resource_url_for_resource(options[:namespace_path], options[:parents], options[:resource_name], format),
|
99
|
+
:mime_type => Mime::Type.lookup_by_extension(format).to_s
|
100
|
+
}.merge(options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def parent_path_for_resource(parents)
|
104
|
+
parents.map { |p| "/#{p}/:id" }.join
|
105
|
+
end
|
106
|
+
|
107
|
+
def collection_url_for_resource(namespace, parents, resource_name, format)
|
108
|
+
[ namespace, parent_path_for_resource(parents), "/#{resource_name}.#{format}" ].join
|
109
|
+
end
|
110
|
+
|
111
|
+
def resource_url_for_resource(namespace, parents, resource_name, format)
|
112
|
+
[ namespace, parent_path_for_resource(parents), "/#{resource_name}/:id.#{format}" ].join
|
113
|
+
end
|
114
|
+
|
115
|
+
def resources(resource_name, options = {}, &blk)
|
116
|
+
options = metadata_for_resources(resource_name, options)
|
117
|
+
_resource_name = options[:resource_name].singularize
|
118
|
+
|
119
|
+
# If you want xml, pass it as formats: [ :json, :xml ]
|
120
|
+
options[:formats].each do |format|
|
121
|
+
format_options = metadata_for_formatted_resource(format, options)
|
122
|
+
context format_options[:collection_url], format_options do
|
123
|
+
let(:namespace) { options[:namespace] }
|
124
|
+
let(:resource_name) { _resource_name }
|
125
|
+
let(:parent_names) { options[:parents] }
|
126
|
+
let(:format) { format }
|
127
|
+
let(options[:parents].last) { model_parent } if options[:parents].last
|
128
|
+
instance_eval(&blk) if blk
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def expects_resource_crud(options = {}, &additional_examples)
|
134
|
+
expected_actions = options[:only] || [ :index, :show, :create, :update, :destroy ]
|
135
|
+
expected_actions -= options[:except] if options[:except]
|
136
|
+
|
137
|
+
expected_actions.each do |action|
|
138
|
+
send("expects_#{action}")
|
139
|
+
end
|
140
|
+
|
141
|
+
instance_eval(&additional_examples) if additional_examples
|
142
|
+
end
|
143
|
+
|
144
|
+
STANDARD_SUCCESSFUL_STATUS_FOR = {
|
145
|
+
:index => 200,
|
146
|
+
:show => 200,
|
147
|
+
:create => 201,
|
148
|
+
:update => 200,
|
149
|
+
:destroy => 204 }
|
150
|
+
|
151
|
+
STANDARD_REQUEST_FOR = {
|
152
|
+
:index => { :method => :get, :end_point => :collection_url },
|
153
|
+
:show => { :method => :get, :end_point => :resource_url },
|
154
|
+
:create => { :method => :post, :end_point => :collection_url, :payload => proc do valid_create_attributes end },
|
155
|
+
:update => { :method => :put, :end_point => :resource_url, :payload => proc do valid_update_attributes end },
|
156
|
+
:destroy => { :method => :delete, :end_point => :resource_url } }
|
157
|
+
|
158
|
+
def request_resource_action(action, options = {}, &blk)
|
159
|
+
options = {
|
160
|
+
:mime_type => metadata[:mime_type],
|
161
|
+
:encoding => metadata[:encoding],
|
162
|
+
:status => STANDARD_SUCCESSFUL_STATUS_FOR[action],
|
163
|
+
:collection_url => metadata[:collection_url],
|
164
|
+
:resource_url => metadata[:resource_url]
|
165
|
+
}.merge(STANDARD_REQUEST_FOR[action]).merge(options)
|
166
|
+
|
167
|
+
request options[:method], options[options[:end_point]], options[:payload] do
|
168
|
+
let(:request_url) { send(options[:end_point]) }
|
169
|
+
expects_status(options[:status])
|
170
|
+
expects_content_type(options[:mime_type], options[:encoding])
|
171
|
+
instance_eval(&blk) if blk
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def expects_index(options = {}, &additional_examples)
|
176
|
+
request_resource_action(:index, options) do
|
177
|
+
unless options[:skip_presentation_test]
|
178
|
+
it "should return a list of all #{metadata[:resource_name]}" do
|
179
|
+
reset_datastore!
|
180
|
+
collection.should_not be_empty
|
181
|
+
body.should eql(presented_collection)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
expects_unauthorized_access_to_respond_with_401
|
186
|
+
unless options[:skip_pagination_examples]
|
187
|
+
expects_paginated_resource do
|
188
|
+
before(:each) { reset_datastore! }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
instance_eval(&additional_examples) if additional_examples
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def expects_show(options = {}, &additional_examples)
|
196
|
+
request_resource_action(:show, options) do
|
197
|
+
it "should return a #{metadata[:resource_name]} of id 1" do
|
198
|
+
resource.should_not be_nil
|
199
|
+
body.should eql(resource)
|
200
|
+
end
|
201
|
+
|
202
|
+
with_non_existent_resource_should_respond_with_404
|
203
|
+
expects_unauthorized_access_to_respond_with_401
|
204
|
+
instance_eval(&additional_examples) if additional_examples
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def expects_create(options = {}, &additional_examples)
|
209
|
+
request_resource_action(:create, options) do
|
210
|
+
it "should return the newly created #{metadata[:resource_name]}" do
|
211
|
+
body.should eql(parser.decode(resource_after_create.send("to_#{format}", { :presenter => presenter, :scope => presenter_scope})))
|
212
|
+
end
|
213
|
+
|
214
|
+
with_malformed_data_should_respond_with_400
|
215
|
+
expects_unauthorized_access_to_respond_with_401
|
216
|
+
instance_eval(&additional_examples) if additional_examples
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def expects_update(options = {}, &additional_examples)
|
221
|
+
request_resource_action(:update, options) do
|
222
|
+
it "should update #{metadata[:resource_name]}" do
|
223
|
+
response.should_not be(nil)
|
224
|
+
valid_update_attributes.each do |updated_attribute, updated_value|
|
225
|
+
resource_after_update[updated_attribute].should eql(updated_value)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
with_malformed_data_should_respond_with_400
|
230
|
+
with_non_existent_resource_should_respond_with_404
|
231
|
+
expects_unauthorized_access_to_respond_with_401
|
232
|
+
instance_eval(&additional_examples) if additional_examples
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def expects_destroy(options = {}, &additional_examples)
|
237
|
+
request_resource_action(:destroy, {mime_type: nil, encoding: nil}.merge(options)) do
|
238
|
+
it "should delete #{metadata[:resource_name]}" do
|
239
|
+
response.should_not be(nil)
|
240
|
+
lambda { resource_after_destroy }.should raise_error(record_not_found_error)
|
241
|
+
end
|
242
|
+
|
243
|
+
with_non_existent_resource_should_respond_with_404
|
244
|
+
expects_unauthorized_access_to_respond_with_401
|
245
|
+
instance_eval(&additional_examples) if additional_examples
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def with_malformed_data_should_respond_with_400
|
250
|
+
context "with malformed #{metadata[:format]} payload" do
|
251
|
+
let(:request_raw_payload) { send("malformed_#{format}_payload") }
|
252
|
+
|
253
|
+
expects_status(400)
|
254
|
+
expects_content_type(metadata[:mime_type], metadata[:encoding])
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def with_non_existent_resource_should_respond_with_404
|
259
|
+
context 'with non-existent resource' do
|
260
|
+
let(:resource_id) { 0 } # Assumes that persisted datastore never uses id of 0
|
261
|
+
expects_status 404
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def expects_json_presentation(_presenter_scope = nil)
|
266
|
+
let(:presenter_scope) { _presenter_scope } if _presenter_scope
|
267
|
+
|
268
|
+
it 'should present a JSON object' do
|
269
|
+
body.should eql(presented_resource)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Intermodal
|
2
|
+
module RSpec
|
3
|
+
module HTTP
|
4
|
+
RFC2616_HTTP_STATUS_CODES =
|
5
|
+
[[100, "Continue"],
|
6
|
+
[101, "Switching Protocols"],
|
7
|
+
[200, "OK"],
|
8
|
+
[201, "Created"],
|
9
|
+
[202, "Accepted"],
|
10
|
+
[203, "Non-Authoritative Information"],
|
11
|
+
[204, "No Content"],
|
12
|
+
[205, "Reset Content"],
|
13
|
+
[206, "Partial Content"],
|
14
|
+
[300, "Multiple Choices"],
|
15
|
+
[301, "Moved Permanently"],
|
16
|
+
[302, "Found"],
|
17
|
+
[303, "See Other"],
|
18
|
+
[304, "Not Modified"],
|
19
|
+
[305, "Use Proxy"],
|
20
|
+
[306, "(Unused)"],
|
21
|
+
[307, "Temporary Redirect"],
|
22
|
+
[400, "Bad Request"],
|
23
|
+
[401, "Unauthorized"],
|
24
|
+
[402, "Payment Required"],
|
25
|
+
[403, "Forbidden"],
|
26
|
+
[404, "Not Found"],
|
27
|
+
[405, "Method Not Allowed"],
|
28
|
+
[406, "Not Acceptable"],
|
29
|
+
[407, "Proxy Authentication Required"],
|
30
|
+
[408, "Request Timeout"],
|
31
|
+
[409, "Conflict"],
|
32
|
+
[410, "Gone"],
|
33
|
+
[411, "Length Required"],
|
34
|
+
[412, "Precondition Failed"],
|
35
|
+
[413, "Request Entity Too Large"],
|
36
|
+
[414, "Request-URI Too Long"],
|
37
|
+
[415, "Unsupported Media Type"],
|
38
|
+
[416, "Requested Range Not Satisfiable"],
|
39
|
+
[417, "Expectation Failed"],
|
40
|
+
[422, "Unprocessable Entity"],
|
41
|
+
[500, "Internal Server Error"],
|
42
|
+
[501, "Not Implemented"],
|
43
|
+
[502, "Bad Gateway"],
|
44
|
+
[503, "Service Unavailable"],
|
45
|
+
[504, "Gateway Timeout"],
|
46
|
+
[505, "HTTP Version Not Supported"]]
|
47
|
+
|
48
|
+
STATUS_CODES = RFC2616_HTTP_STATUS_CODES.inject({}) { |h, e| h[e[0].to_s]=e[1]; h }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Intermodal
|
4
|
+
module RSpec
|
5
|
+
module Validators
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Make sure to define:
|
10
|
+
# let(:resource) # resource to be tested
|
11
|
+
# let(:associated_resource) # association and resource both owned by same account
|
12
|
+
# let(:invalid_associated_resource) # association and resource owned by different account
|
13
|
+
#
|
14
|
+
# Optional
|
15
|
+
# let(:attribute) # Attribute being validated
|
16
|
+
# let(:message) # Error message
|
17
|
+
#
|
18
|
+
def self.validates_same_account(association, &additional_examples)
|
19
|
+
context "validates #{association} is owned by same account" do
|
20
|
+
let(:attribute) { association }
|
21
|
+
let(:message) { 'must belong to the same account' }
|
22
|
+
|
23
|
+
context 'when association belongs to same account' do
|
24
|
+
it 'should be_valid' do
|
25
|
+
expect(resource.account_id).to eql(associated_resource.account_id)
|
26
|
+
expect(resource).to be_valid
|
27
|
+
expect(resource.errors).not_to have_key(attribute)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when association belongs to a different account' do
|
32
|
+
before(:each) { resource.send("#{association}=", invalid_associated_resource) }
|
33
|
+
|
34
|
+
it 'should not be valid and report the error' do
|
35
|
+
expect(resource.account_id).not_to eql(invalid_associated_resource.account_id)
|
36
|
+
expect(resource).not_to be_valid
|
37
|
+
expect(resource.errors).to have_key(attribute)
|
38
|
+
expect(resource.errors[attribute]).to include(message)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
instance_eval(&additional_examples) if additional_examples
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Make sure to define:
|
47
|
+
# let(:resource) # resource to be tested
|
48
|
+
# let(:associated_resource) # association and resource owned by different accounts
|
49
|
+
# let(:invalid_associated_resource) # association and resource both owned by same account
|
50
|
+
#
|
51
|
+
# Optional
|
52
|
+
# let(:attribute) # Attribute being validated
|
53
|
+
# let(:message) # Error message
|
54
|
+
#
|
55
|
+
def self.validates_different_account(association, &additional_examples)
|
56
|
+
context "validates #{association} is owned by a different account" do
|
57
|
+
let(:attribute) { association }
|
58
|
+
let(:message) { 'must belong to a different account' }
|
59
|
+
|
60
|
+
context 'when association belongs to same account' do
|
61
|
+
before(:each) { resource.send("#{association}=", invalid_associated_resource) }
|
62
|
+
|
63
|
+
it 'should not be valid' do
|
64
|
+
expect(resource.account_id).to eql(invalid_associated_resource.account_id)
|
65
|
+
expect(resource).not_to be_valid
|
66
|
+
expect(resource.errors).to have_key(attribute)
|
67
|
+
expect(resource.errors[attribute]).to include(message)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when association belongs to a different account' do
|
72
|
+
|
73
|
+
it 'should not be valid and report the error' do
|
74
|
+
expect(resource.account_id).not_to eql(associated_resource.account_id)
|
75
|
+
expect(resource).to be_valid
|
76
|
+
expect(resource.errors).not_to have_key(attribute)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
instance_eval(&additional_examples) if additional_examples
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|