intermodal 0.0.1 → 0.4.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.
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