intermodal 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +1 -1
  5. data/README +6 -4
  6. data/Rakefile +6 -0
  7. data/intermodal.gemspec +51 -0
  8. data/lib/generators/intermodal_generator.rb +8 -0
  9. data/lib/intermodal.rb +122 -0
  10. data/lib/intermodal/api.rb +246 -0
  11. data/lib/intermodal/api/configuration.rb +40 -0
  12. data/lib/intermodal/api/railties.rb +21 -0
  13. data/lib/intermodal/concerns/acceptors/named_resource.rb +12 -0
  14. data/lib/intermodal/concerns/acceptors/resource.rb +12 -0
  15. data/lib/intermodal/concerns/controllers/accountability.rb +17 -0
  16. data/lib/intermodal/concerns/controllers/anonymous.rb +24 -0
  17. data/lib/intermodal/concerns/controllers/authenticatable.rb +24 -0
  18. data/lib/intermodal/concerns/controllers/paginated_collection.rb +25 -0
  19. data/lib/intermodal/concerns/controllers/presentation.rb +17 -0
  20. data/lib/intermodal/concerns/controllers/resource.rb +51 -0
  21. data/lib/intermodal/concerns/controllers/resource_linking.rb +58 -0
  22. data/lib/intermodal/concerns/let.rb +21 -0
  23. data/lib/intermodal/concerns/models/access_credential.rb +28 -0
  24. data/lib/intermodal/concerns/models/account.rb +16 -0
  25. data/lib/intermodal/concerns/models/accountability.rb +47 -0
  26. data/lib/intermodal/concerns/models/db_access_token.rb +29 -0
  27. data/lib/intermodal/concerns/models/has_parent_resource.rb +45 -0
  28. data/lib/intermodal/concerns/models/presentation.rb +25 -0
  29. data/lib/intermodal/concerns/models/redis_access_token.rb +60 -0
  30. data/lib/intermodal/concerns/models/resource_linking.rb +126 -0
  31. data/lib/intermodal/concerns/models/sanitize_html.rb +35 -0
  32. data/lib/intermodal/concerns/presenters/named_resource.rb +12 -0
  33. data/lib/intermodal/concerns/presenters/resource.rb +14 -0
  34. data/lib/intermodal/concerns/rails/rails_3_stack.rb +42 -0
  35. data/lib/intermodal/concerns/rails/rails_4_stack.rb +17 -0
  36. data/lib/intermodal/concerns/rails/use_warden.rb +21 -0
  37. data/lib/intermodal/config.rb +15 -0
  38. data/lib/intermodal/configuration.rb +11 -0
  39. data/lib/intermodal/controllers/api_controller.rb +26 -0
  40. data/lib/intermodal/controllers/linking_resource_controller.rb +8 -0
  41. data/lib/intermodal/controllers/nested_resource_controller.rb +18 -0
  42. data/lib/intermodal/controllers/resource_controller.rb +11 -0
  43. data/lib/intermodal/dsl/controllers.rb +125 -0
  44. data/lib/intermodal/dsl/mapping.rb +79 -0
  45. data/lib/intermodal/dsl/presentation_helpers.rb +107 -0
  46. data/lib/intermodal/mapping/acceptor.rb +2 -2
  47. data/lib/intermodal/mapping/mapper.rb +39 -13
  48. data/lib/intermodal/mapping/presenter.rb +12 -6
  49. data/lib/intermodal/proxies/linking_resources.rb +58 -0
  50. data/lib/intermodal/proxies/will_paginate.rb +85 -0
  51. data/lib/intermodal/rack/auth.rb +29 -0
  52. data/lib/intermodal/rack/dummy_store.rb +24 -0
  53. data/lib/intermodal/rack/rescue.rb +82 -0
  54. data/lib/intermodal/responders/linking_resource_responder.rb +21 -0
  55. data/lib/intermodal/responders/resource_responder.rb +64 -0
  56. data/lib/intermodal/rspec/acceptors.rb +79 -0
  57. data/lib/intermodal/rspec/models/accountability.rb +114 -0
  58. data/lib/intermodal/rspec/models/has_parent_resource.rb +132 -0
  59. data/lib/intermodal/rspec/models/resource_linking.rb +234 -0
  60. data/lib/intermodal/rspec/models/sanitization.rb +84 -0
  61. data/lib/intermodal/rspec/presenters.rb +92 -0
  62. data/lib/intermodal/rspec/requests/authenticated_requests.rb +17 -0
  63. data/lib/intermodal/rspec/requests/linked_resources.rb +180 -0
  64. data/lib/intermodal/rspec/requests/paginated_collection.rb +60 -0
  65. data/lib/intermodal/rspec/requests/rack.rb +142 -0
  66. data/lib/intermodal/rspec/requests/request_validations.rb +36 -0
  67. data/lib/intermodal/rspec/requests/resources.rb +275 -0
  68. data/lib/intermodal/rspec/requests/rfc2616_status_codes.rb +51 -0
  69. data/lib/intermodal/rspec/validators.rb +86 -0
  70. data/lib/intermodal/validators/account_validator.rb +27 -0
  71. data/lib/intermodal/validators/different_account_validator.rb +27 -0
  72. data/lib/intermodal/version.rb +3 -0
  73. data/spec/mapping/acceptors_spec.rb +142 -0
  74. data/spec/mapping/presenters_spec.rb +186 -0
  75. data/spec/models/accountability_spec.rb +13 -0
  76. data/spec/models/has_parent_resource_spec.rb +18 -0
  77. data/spec/models/resource_linking_spec.rb +21 -0
  78. data/spec/proxies/will_paginate_spec.rb +163 -0
  79. data/spec/rack/auth_spec.rb +51 -0
  80. data/spec/requests/linked_resources.rb +37 -0
  81. data/spec/requests/nested_resources_spec.rb +54 -0
  82. data/spec/requests/resources_spec.rb +50 -0
  83. data/spec/spec_helper.rb +53 -0
  84. data/spec/support/api.rb +50 -0
  85. data/spec/support/app/class_builder.rb +41 -0
  86. data/spec/support/app/db/adapter_helper.rb +53 -0
  87. data/spec/support/app/db/authentication_schema_helper.rb +62 -0
  88. data/spec/support/app/db/migration_helper.rb +44 -0
  89. data/spec/support/app/schema.rb +101 -0
  90. data/spec/support/application.rb +23 -0
  91. data/spec/support/blueprints.rb +41 -0
  92. data/spec/support/epiphyte.rb +29 -0
  93. metadata +393 -52
  94. data/lib/intermodal/base.rb +0 -13
  95. data/lib/intermodal/declare_controllers.rb +0 -102
  96. data/lib/intermodal/mapping.rb +0 -4
  97. data/lib/intermodal/mapping/dsl.rb +0 -76
@@ -0,0 +1,84 @@
1
+ module Intermodal
2
+ module RSpec
3
+ module Sanitization
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def expects_sanitization_of(_field, _options, &additional_examples)
8
+ # We are not trying to retest the sanitizer so much as lightly demonstrating
9
+ # idempotence. That is, repeated calls to the sanitizer should produce the
10
+ # same output
11
+
12
+ context _field.inspect do
13
+ subject { resource.update_attributes!(updated_attributes); resource }
14
+ let(:updated_attributes) { { _field => value } }
15
+ let(:accepted_tags) { _options[:accepted_tags] }
16
+ let(:rejected_tags) { _options[:rejected_tags] }
17
+
18
+ context 'with a random string' do
19
+ let(:value) { SecureRandom.hex(16) }
20
+ it 'should leave it alone' do
21
+ expect(subject).not_to be_changed # Check update has persisted
22
+ expect(subject.send(_field)).to eql(value)
23
+ end
24
+ end
25
+
26
+ context 'with approved html tag' do
27
+ let(:tag) { accepted_tags.sample }
28
+ let(:content) { SecureRandom.hex(16) }
29
+ let(:value) { "<#{tag}>#{content}</#{tag}>" }
30
+ it 'should leave it alone' do
31
+ expect(subject).not_to be_changed # Check update has persisted
32
+ expect(subject.send(_field)).to eql(value)
33
+ end
34
+ end
35
+
36
+ context 'with tag not on whitelist' do
37
+ let(:tag) { rejected_tags.sample }
38
+ let(:content) { SecureRandom.hex(16) }
39
+ let(:value) { "<#{tag}>#{content}</#{tag}>" }
40
+ it 'should sanitize tag' do
41
+ expect(subject).not_to be_changed # Check update has persisted
42
+ expect(subject.send(_field)).to eql(content)
43
+ end
44
+ end
45
+
46
+ instance_eval(&additional_examples) if additional_examples
47
+ end
48
+ end
49
+
50
+ def expects_stripping_of(_field, &additional_examples)
51
+ # We are not trying to retest the sanitizer so much as lightly demonstrating
52
+ # idempotence. That is, repeated calls to the sanitizer should produce the
53
+ # same output
54
+
55
+ context _field.inspect do
56
+ subject { resource.update_attributes!(updated_attributes); resource }
57
+ let(:updated_attributes) { { _field => value } }
58
+ let(:rejected_tags) { %w(p div span ol ul li em strong) }
59
+
60
+ context 'with a random string' do
61
+ let(:value) { SecureRandom.hex(16) }
62
+ it 'should leave it alone' do
63
+ expect(subject).not_to be_changed # Check update has persisted
64
+ expect(subject.send(_field)).to eql(value)
65
+ end
66
+ end
67
+
68
+ context 'with any tag' do
69
+ let(:tag) { rejected_tags.sample }
70
+ let(:content) { SecureRandom.hex(16) }
71
+ let(:value) { "<#{tag}>#{content}</#{tag}>" }
72
+ it 'should sanitize tag' do
73
+ expect(subject).not_to be_changed # Check update has persisted
74
+ expect(subject.send(_field)).to eql(content)
75
+ end
76
+ end
77
+
78
+ instance_eval(&additional_examples) if additional_examples
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,92 @@
1
+ module Intermodal
2
+ module RSpec
3
+ module Presenters
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ extend ::Intermodal::RSpec::Macros::Presenters
7
+
8
+ let(:resource) { model.make! }
9
+ let(:resource_name) { model.name.demodulize.underscore }
10
+ let(:presenter) { api.presenter_for(model) }
11
+ let(:presentation) { presenter.call(resource, :scope => scope).with_indifferent_access }
12
+ let(:scope) { :default }
13
+
14
+ def expects_presentation(input, expectation)
15
+ field = input.keys[0]
16
+ presenter.call(input.with_indifferent_access, :scope => scope)[field].should eql(expectation)
17
+ end
18
+ end
19
+ end
20
+
21
+ module Macros
22
+ module Presenters
23
+ def concerned_with_presentation(_model, &blk)
24
+ describe _model do
25
+ let(:model) { _model }
26
+ subject { model }
27
+
28
+ context "when concerned with presentation" do
29
+ instance_eval(&blk) if blk
30
+ end
31
+ end
32
+ end
33
+
34
+ def scoped_to(_scope, &blk)
35
+ context "when scoped to #{_scope}" do
36
+ let(:scope) { _scope }
37
+ instance_eval(&blk) if blk
38
+ end
39
+ end
40
+
41
+ def exposes(*fields)
42
+ fields.each do |field|
43
+ it "should present #{field}" do
44
+ presentation.should contain(field)
45
+ end
46
+ end
47
+ end
48
+
49
+ def hides(*fields)
50
+ fields.each do |field|
51
+ it "should not present #{field}" do
52
+ presentation.should_not contain(field)
53
+ end
54
+ end
55
+ end
56
+
57
+ def exposes_resource
58
+ context 'when exposing resource fields' do
59
+ exposes 'id', 'created_at', 'updated_at'
60
+ end
61
+ end
62
+
63
+ def exposes_named_resource
64
+ context 'when exposing named resource fields' do
65
+ exposes_resource
66
+ exposes 'name'
67
+ end
68
+ end
69
+
70
+ def exposes_linked_resources(linked_resources_name, options = {})
71
+ _linked_resource_name, _linking_association = linked_resources_name.to_s.singularize, options[:with]
72
+ context "when exposing linked resources, #{_linked_resource_name}" do
73
+ let(:linked_resource_ids_name) { :"#{_linked_resource_name}_ids" }
74
+ let(:linking_association) { :"#{_linking_association}" }
75
+ let(:linked_resource_ids) { resource.send(linking_association).send("to_#{linked_resource_ids_name}").to_a }
76
+ let(:presented_linked_ids) { presentation[linked_resource_ids_name].to_a }
77
+
78
+ exposes "#{_linked_resource_name}_ids"
79
+ it "should present \"#{_linked_resource_name}_ids\" as a collection of ids" do
80
+ resource
81
+ (presented_linked_ids - linked_resource_ids).should be_empty
82
+ (linked_resource_ids - presented_linked_ids).should be_empty
83
+ end
84
+
85
+ pending "should only present \"#{_linked_resource_name}_ids\" scoped to account"
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,17 @@
1
+ module Intermodal
2
+ module RSpec
3
+ module AuthenticatedRequests
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def expects_unauthorized_access_to_respond_with_401
8
+ context 'with unauthorized access credentials' do
9
+ let(:http_headers) { { 'X-Auth-Token' => '', 'Accept' => 'application/json' } }
10
+
11
+ expects_status(401)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,180 @@
1
+ module Intermodal
2
+ module RSpec
3
+ module LinkedResources
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Intermodal::RSpec::Resources
8
+ end
9
+
10
+ module ClassMethods
11
+ def link_resource(parent_resource, options = {}, &blk)
12
+ metadata[:parent_resource] = options[:parent_resource] = parent_resource
13
+ metadata[:target_resources] = options[:to]
14
+ metadata[:linking_resource_model] = options[:with]
15
+
16
+ _target_resources = options[:to]
17
+
18
+ resource_options = {
19
+ :namespace => options[:namespace]
20
+ }
21
+ resources [metadata[:parent_resource].to_s.pluralize, metadata[:target_resources]], resource_options do
22
+ let(:model) { options[:with] }
23
+ let(:model_parents) { [ send(options[:parent_resource]) ] }
24
+ let(:unlinked_targets) { raise "Override :unlinked_targets with let()" }
25
+ let(:target_resources) { _target_resources }
26
+ let(:collection_element_name) { "#{target_resources.to_s.singularize}_ids" }
27
+ let(:paginated_collection) { collection }
28
+ let(:resource_element_name) { parent_resource }
29
+ let(:presentation_root) { options[:parent_resource] }
30
+ let(:collection_proxy) do
31
+ Intermodal::Proxies::LinkingResources.new presentation_root,
32
+ :to => target_resources,
33
+ :with => collection,
34
+ :parent_id => model_parents.first.id
35
+ end
36
+ let(:presented_collection) { parser.decode(collection_proxy.send("to_#{format}")) }
37
+ instance_eval(&blk)
38
+ end
39
+ end
40
+
41
+ def expects_crud_for_linked_resource
42
+ expects_index :skip_pagination_examples => true do
43
+ it 'should include the parent id' do
44
+ collection.should_not be_empty
45
+ body[resource_element_name.to_s]['id'].should eql(model_parents.first.id)
46
+ end
47
+ end
48
+
49
+ expects_list_replace
50
+ expects_list_append
51
+ expects_list_remove
52
+ end
53
+
54
+ def expects_list_replace(&blk)
55
+ _metadata = metadata
56
+ request :post, metadata[:collection_url] do
57
+ let(:request_url) { collection_url }
58
+ let(:replacement_target_ids) { unlinked_targets.map(&:id) }
59
+ let(:request_payload) { { collection_element_name => replacement_target_ids } }
60
+ let(:updated_target_ids) { model.by_parent(model_parent).to_target_ids }
61
+
62
+ instance_eval(&blk) if blk
63
+
64
+ expects_status(201)
65
+ expects_content_type(metadata[:mime_type], metadata[:encoding])
66
+
67
+ with_malformed_data_should_respond_with_400
68
+ with_nil_target_ids_should_respond_with_422
69
+ expects_unauthorized_access_to_respond_with_401
70
+
71
+ context 'with empty payload' do
72
+ let(:request_payload) { { collection_element_name => [] } }
73
+
74
+ expects_status(201)
75
+ expects_content_type(metadata[:mime_type], metadata[:encoding])
76
+
77
+ it 'should reset resource linking' do
78
+ response.should_not be_empty
79
+ updated_target_ids.should be_empty
80
+ end
81
+ end
82
+
83
+ it "should link #{metadata[:target_resources]} to #{metadata[:parent_resource]}" do
84
+ model_collection.should_not be_empty
85
+ response.should_not be_empty
86
+ replacement_target_ids.each do |replacement_target_id|
87
+ updated_target_ids.should include(replacement_target_id)
88
+ end
89
+ end
90
+
91
+ it "should delete original linked #{metadata[:target_resources]}" do
92
+ model_collection.should_not be_empty
93
+ response.should_not be_empty
94
+ model_collection.each do |original_target_id|
95
+ updated_target_ids.should_not include(original_target_id)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ def expects_list_append(&blk)
102
+ _metadata = metadata
103
+ request :put, metadata[:collection_url] do
104
+ instance_eval(&blk) if blk
105
+ let(:request_url) { collection_url }
106
+ let(:additional_target_ids) { unlinked_targets.map(&:id) }
107
+ let(:request_payload) { { collection_element_name => additional_target_ids } }
108
+ let(:updated_target_ids) { model.by_parent(model_parent).to_target_ids }
109
+
110
+ expects_status(200)
111
+ expects_content_type(metadata[:mime_type], metadata[:encoding])
112
+
113
+ it 'should link additional targets to manufacturer' do
114
+ model_collection.should_not be_empty
115
+ response.should_not be_empty
116
+ additional_target_ids.each do |additional_target_id|
117
+ updated_target_ids.should include(additional_target_id)
118
+ end
119
+ end
120
+
121
+ it 'should append to, but not delete original linked targets' do
122
+ model_collection.should_not be_empty
123
+ response.should_not be_empty
124
+ model_collection.each do |original_target_id|
125
+ updated_target_ids.should include(original_target_id)
126
+ end
127
+ end
128
+
129
+ with_malformed_data_should_respond_with_400
130
+ with_nil_target_ids_should_respond_with_422
131
+ expects_unauthorized_access_to_respond_with_401
132
+ end
133
+ end
134
+
135
+ def expects_list_remove(&blk)
136
+ _metadata = metadata
137
+ request :delete, metadata[:collection_url] do
138
+ instance_eval(&blk) if blk
139
+ let(:request_url) { collection_url }
140
+ let(:deleted_target_ids) { model_collection[0..1] }
141
+ let(:remaining_target_ids) { model_collection - deleted_target_ids }
142
+ let(:request_payload) { { collection_element_name => deleted_target_ids } }
143
+ let(:updated_target_ids) { model.by_parent(model_parent).to_target_ids }
144
+
145
+ expects_status(200)
146
+ expects_content_type(metadata[:mime_type], metadata[:encoding])
147
+
148
+ it 'should delete linked targets' do
149
+ deleted_target_ids.should_not be_empty
150
+ response.should_not be_empty
151
+ deleted_target_ids.each do |deleted_target_id|
152
+ updated_target_ids.should_not include(deleted_target_id)
153
+ end
154
+ end
155
+
156
+ it 'should keep remaining targets' do
157
+ deleted_target_ids.should_not be_empty
158
+ response.should_not be_empty
159
+ remaining_target_ids.each do |remaining_target_id|
160
+ updated_target_ids.should include(remaining_target_id)
161
+ end
162
+ end
163
+
164
+ with_malformed_data_should_respond_with_400
165
+ with_nil_target_ids_should_respond_with_422
166
+ expects_unauthorized_access_to_respond_with_401
167
+ end
168
+ end
169
+
170
+ def with_nil_target_ids_should_respond_with_422
171
+ context 'without nil ids ' do
172
+ let(:request_payload) { { collection_element_name => nil } }
173
+ expects_status(422)
174
+ end
175
+ end
176
+
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,60 @@
1
+ module Intermodal
2
+ module RSpec
3
+ module PaginatedCollection
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def expects_paginated_resource(options = {}, &customizations)
8
+ # Default behavior for will_paginate
9
+ options[:page] ||= 1
10
+ options[:collection_name]
11
+
12
+ context 'when paginating' do
13
+ let(:expected_total_pages) { collection.size/per_page + 1 }
14
+ let(:expected_total_entries) { collection.size }
15
+ let(:collection_element_name) { options[:collection_name] } if options[:collection_name]
16
+ let(:responded_collection_metadata) do
17
+ case format
18
+ when :xml
19
+ body[collection_element_name.to_s]
20
+ else
21
+ body
22
+ end
23
+ end
24
+
25
+ instance_eval(&customizations) if customizations
26
+
27
+ if options[:empty_collection]
28
+ it 'should have an empty collection' do
29
+ body[collection_element_name.to_s].should be_empty
30
+ end
31
+ else
32
+ it 'should have a collection' do
33
+ collection
34
+ body[collection_element_name.to_s].should_not be_empty
35
+ end
36
+ end
37
+
38
+ it "should be on page #{options[:page]}" do
39
+ collection
40
+ responded_collection_metadata['page'].should eql(options[:page])
41
+ end
42
+
43
+ it 'should have total_pages' do
44
+ collection
45
+ responded_collection_metadata['total_pages'].should eql(expected_total_pages)
46
+ end
47
+
48
+ it 'should have total_entries' do
49
+ collection
50
+ responded_collection_metadata['total_entries'].should eql(expected_total_entries)
51
+ end
52
+ end
53
+ end
54
+
55
+ # Some version of WillPaginate steps on this
56
+ alias expects_pagination expects_paginated_resource
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,142 @@
1
+ module Intermodal
2
+ module RSpec
3
+ module Rack
4
+ extend ActiveSupport::Concern
5
+
6
+ class SimpleXMLParser
7
+ def self.decode(*args)
8
+ Hash.from_xml(*args)
9
+ end
10
+ end
11
+
12
+ included do
13
+ subject { response }
14
+
15
+ # Define the Rack Application you are testing
16
+ let(:application) { fail NotImplementedError, 'Must define let(:application)' }
17
+ let(:http_headers) { Hash.new }
18
+ # let(:request_headers) { rack_compliant_headers(http_headers) }
19
+ let(:request_url_prefix) { '' }
20
+
21
+ let(:response) { application.call(request) }
22
+ let(:status) { response[0] }
23
+ let(:response_headers) { response[1] }
24
+ let(:content_type) { response_headers['Content-Type'] }
25
+ let(:raw_body) do
26
+ # http://rack.rubyforge.org/doc/files/SPEC.html
27
+ # Rack Spec says response[2] should respond to #each and we
28
+ # cannot gaurantee this is an ActionDispatch::Response
29
+
30
+ # This calls each on all the chunks in-memory and gives it back as a whole string
31
+
32
+ [].tap do |out|
33
+ response[2].each do |chunk|
34
+ out << chunk
35
+ end
36
+ end.join
37
+ end
38
+ let(:body) { parser.decode(raw_body) }
39
+
40
+ let(:json_parser) { MultiJson }
41
+ let(:xml_parser) { SimpleXMLParser }
42
+
43
+ let(:parser) do
44
+ case format
45
+ when :json
46
+ json_parser
47
+ when :xml
48
+ xml_parser
49
+ end
50
+ end
51
+
52
+ # Override to change payload Content-Type
53
+ let(:request_payload_mime_type) { Mime::Type.lookup_by_extension(format).to_s }
54
+
55
+ # Override to change JSON payload builder
56
+ let(:request_json_payload) { request_payload.to_json }
57
+
58
+ # Override to change XML payload builder
59
+ let(:request_xml_payload) { request_payload.to_xml }
60
+
61
+ # Override to send arbitrary payloads
62
+ let(:request_raw_payload) { send("request_#{format}_payload") unless request_payload.nil? }
63
+
64
+ let(:expected_mime_type) { Mime::Types.lookup_by_extension(format).to_s }
65
+ let(:expected_encoding) { 'utf-8' }
66
+
67
+ # Broken out so you can use this outside of let()
68
+ def rack_compliant_headers(_headers = {})
69
+ _headers.to_a.inject({}) do |m,kv|
70
+ m.tap do |_m|
71
+ _h, _v = kv
72
+ _m["HTTP_#{_h}".underscore.upcase] = _v
73
+ end
74
+ end
75
+ end
76
+
77
+ def rack_compliant_request(_method, _url, _headers, _body, format = :json)
78
+ _payload_mime_type = (format.is_a?(Symbol) ? Mime::Type.lookup_by_extension(format) : format)
79
+ req_opts = { :method => _method }.merge(rack_compliant_headers(_headers))
80
+ req_opts[:input] = _body
81
+ ::Rack::MockRequest.env_for(_url, req_opts).tap do |_r|
82
+ _r.merge!({ 'CONTENT_TYPE' => _payload_mime_type.to_s }) unless _body.nil?
83
+ end
84
+ end
85
+ end
86
+
87
+ module ClassMethods
88
+ def request(method, _url, payload = nil, &blk)
89
+ describe "#{method.to_s.upcase} #{_url}" do
90
+ rack_request(method, _url, &payload)
91
+ instance_eval(&blk)
92
+ end
93
+ end
94
+
95
+ def rack_request(method, _url, _format = nil, &_payload)
96
+ _format = _format # Scope for closure
97
+ _payload = proc do nil end unless _payload
98
+
99
+ let(:format) { _format } if _format
100
+ let(:request) { rack_compliant_request(method,
101
+ request_url_prefix + request_url,
102
+ http_headers,
103
+ request_raw_payload,
104
+ (request_raw_payload ? request_payload_mime_type : nil) ) }
105
+ let(:request_url) { _url }
106
+ let(:request_payload, &_payload)
107
+ end
108
+
109
+ def expects_status(response_status)
110
+ it "should respond with status #{response_status} #{Intermodal::RSpec::HTTP::STATUS_CODES[response_status.to_s]}" do
111
+ status.should eql(response_status)
112
+ end
113
+ end
114
+
115
+ def expects_content_type(mime_type, charset = nil)
116
+ if mime_type
117
+ it "should respond with content type of #{mime_type}" do
118
+ content_type.should match(%r{^#{mime_type}})
119
+ end
120
+ if charset
121
+ it "should respond encoded in #{charset}" do
122
+ content_type.should match(%r{charset=#{charset}})
123
+ end
124
+ end
125
+ else
126
+ it "should not respond with content type" do
127
+ content_type.should be_nil
128
+ end
129
+ end
130
+
131
+ end
132
+
133
+ def expects_empty_body
134
+ it "should respond with a Rack-compliant empty body" do
135
+ response[2].should be_empty
136
+ response[2].should be_respond_to(:each)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end