hoodoo 1.0.2

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 (216) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hoodoo +5 -0
  3. data/lib/hoodoo.rb +27 -0
  4. data/lib/hoodoo/active.rb +32 -0
  5. data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
  6. data/lib/hoodoo/active/active_record/base.rb +81 -0
  7. data/lib/hoodoo/active/active_record/creator.rb +134 -0
  8. data/lib/hoodoo/active/active_record/dated.rb +343 -0
  9. data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
  10. data/lib/hoodoo/active/active_record/finder.rb +606 -0
  11. data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
  12. data/lib/hoodoo/active/active_record/secure.rb +431 -0
  13. data/lib/hoodoo/active/active_record/support.rb +106 -0
  14. data/lib/hoodoo/active/active_record/translated.rb +87 -0
  15. data/lib/hoodoo/active/active_record/uuid.rb +80 -0
  16. data/lib/hoodoo/active/active_record/writer.rb +321 -0
  17. data/lib/hoodoo/client.rb +23 -0
  18. data/lib/hoodoo/client/augmented_array.rb +29 -0
  19. data/lib/hoodoo/client/augmented_base.rb +168 -0
  20. data/lib/hoodoo/client/augmented_hash.rb +23 -0
  21. data/lib/hoodoo/client/client.rb +354 -0
  22. data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
  23. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
  24. data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
  25. data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
  26. data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
  27. data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
  28. data/lib/hoodoo/client/headers.rb +269 -0
  29. data/lib/hoodoo/communicators.rb +23 -0
  30. data/lib/hoodoo/communicators/fast.rb +44 -0
  31. data/lib/hoodoo/communicators/pool.rb +601 -0
  32. data/lib/hoodoo/communicators/slow.rb +84 -0
  33. data/lib/hoodoo/data.rb +51 -0
  34. data/lib/hoodoo/data/resources/caller.rb +39 -0
  35. data/lib/hoodoo/data/resources/errors.rb +28 -0
  36. data/lib/hoodoo/data/resources/log.rb +31 -0
  37. data/lib/hoodoo/data/resources/session.rb +26 -0
  38. data/lib/hoodoo/data/types/error_primitive.rb +27 -0
  39. data/lib/hoodoo/data/types/permissions.rb +40 -0
  40. data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
  41. data/lib/hoodoo/data/types/permissions_full.rb +28 -0
  42. data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
  43. data/lib/hoodoo/discovery.rb +20 -0
  44. data/lib/hoodoo/errors.rb +19 -0
  45. data/lib/hoodoo/errors/error_descriptions.rb +229 -0
  46. data/lib/hoodoo/errors/errors.rb +322 -0
  47. data/lib/hoodoo/generator.rb +139 -0
  48. data/lib/hoodoo/logger.rb +23 -0
  49. data/lib/hoodoo/logger/fast_writer.rb +27 -0
  50. data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
  51. data/lib/hoodoo/logger/logger.rb +387 -0
  52. data/lib/hoodoo/logger/slow_writer.rb +49 -0
  53. data/lib/hoodoo/logger/writer_mixin.rb +52 -0
  54. data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
  55. data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
  56. data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
  57. data/lib/hoodoo/middleware.rb +33 -0
  58. data/lib/hoodoo/presenters.rb +45 -0
  59. data/lib/hoodoo/presenters/base.rb +281 -0
  60. data/lib/hoodoo/presenters/base_dsl.rb +519 -0
  61. data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
  62. data/lib/hoodoo/presenters/embedding.rb +232 -0
  63. data/lib/hoodoo/presenters/types/array.rb +118 -0
  64. data/lib/hoodoo/presenters/types/boolean.rb +26 -0
  65. data/lib/hoodoo/presenters/types/date.rb +26 -0
  66. data/lib/hoodoo/presenters/types/date_time.rb +26 -0
  67. data/lib/hoodoo/presenters/types/decimal.rb +47 -0
  68. data/lib/hoodoo/presenters/types/enum.rb +55 -0
  69. data/lib/hoodoo/presenters/types/field.rb +158 -0
  70. data/lib/hoodoo/presenters/types/float.rb +26 -0
  71. data/lib/hoodoo/presenters/types/hash.rb +361 -0
  72. data/lib/hoodoo/presenters/types/integer.rb +26 -0
  73. data/lib/hoodoo/presenters/types/object.rb +117 -0
  74. data/lib/hoodoo/presenters/types/string.rb +53 -0
  75. data/lib/hoodoo/presenters/types/tags.rb +24 -0
  76. data/lib/hoodoo/presenters/types/text.rb +26 -0
  77. data/lib/hoodoo/presenters/types/uuid.rb +54 -0
  78. data/lib/hoodoo/services.rb +34 -0
  79. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
  80. data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
  81. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
  82. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
  83. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
  84. data/lib/hoodoo/services/discovery/discovery.rb +186 -0
  85. data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
  86. data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
  87. data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
  88. data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
  89. data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
  90. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
  91. data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
  92. data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
  93. data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
  94. data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
  95. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
  96. data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
  97. data/lib/hoodoo/services/middleware/interaction.rb +127 -0
  98. data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
  99. data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
  100. data/lib/hoodoo/services/services/context.rb +153 -0
  101. data/lib/hoodoo/services/services/implementation.rb +132 -0
  102. data/lib/hoodoo/services/services/interface.rb +934 -0
  103. data/lib/hoodoo/services/services/permissions.rb +250 -0
  104. data/lib/hoodoo/services/services/request.rb +189 -0
  105. data/lib/hoodoo/services/services/response.rb +316 -0
  106. data/lib/hoodoo/services/services/service.rb +141 -0
  107. data/lib/hoodoo/services/services/session.rb +729 -0
  108. data/lib/hoodoo/utilities.rb +12 -0
  109. data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
  110. data/lib/hoodoo/utilities/utilities.rb +380 -0
  111. data/lib/hoodoo/utilities/uuid.rb +44 -0
  112. data/lib/hoodoo/version.rb +17 -0
  113. data/spec/active/active_record/base_spec.rb +57 -0
  114. data/spec/active/active_record/creator_spec.rb +88 -0
  115. data/spec/active/active_record/dated_spec.rb +248 -0
  116. data/spec/active/active_record/error_mapping_spec.rb +360 -0
  117. data/spec/active/active_record/finder_spec.rb +744 -0
  118. data/spec/active/active_record/search_helper_spec.rb +384 -0
  119. data/spec/active/active_record/secure_spec.rb +435 -0
  120. data/spec/active/active_record/support_spec.rb +225 -0
  121. data/spec/active/active_record/translated_spec.rb +19 -0
  122. data/spec/active/active_record/uuid_spec.rb +72 -0
  123. data/spec/active/active_record/writer_spec.rb +272 -0
  124. data/spec/alchemy/alchemy-amq.rb +33 -0
  125. data/spec/client/augmented_array_spec.rb +15 -0
  126. data/spec/client/augmented_base_spec.rb +50 -0
  127. data/spec/client/augmented_hash_spec.rb +15 -0
  128. data/spec/client/client_spec.rb +955 -0
  129. data/spec/client/endpoint/endpoint_spec.rb +70 -0
  130. data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
  131. data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
  132. data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
  133. data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
  134. data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
  135. data/spec/client/headers_spec.rb +172 -0
  136. data/spec/communicators/fast_spec.rb +9 -0
  137. data/spec/communicators/pool_spec.rb +339 -0
  138. data/spec/communicators/slow_spec.rb +15 -0
  139. data/spec/data/resources/caller_spec.rb +156 -0
  140. data/spec/data/resources/errors_spec.rb +22 -0
  141. data/spec/data/resources/log_spec.rb +20 -0
  142. data/spec/data/resources/session_spec.rb +15 -0
  143. data/spec/data/types/error_primitive_spec.rb +15 -0
  144. data/spec/data/types/permissions_defaults_spec.rb +25 -0
  145. data/spec/data/types/permissions_full_spec.rb +44 -0
  146. data/spec/data/types/permissions_resources_spec.rb +34 -0
  147. data/spec/data/types/permissions_spec.rb +37 -0
  148. data/spec/errors/error_descriptions_spec.rb +98 -0
  149. data/spec/errors/errors_spec.rb +346 -0
  150. data/spec/integration/service_actions_spec.rb +112 -0
  151. data/spec/logger/fast_writer_spec.rb +18 -0
  152. data/spec/logger/logger_spec.rb +259 -0
  153. data/spec/logger/slow_writer_spec.rb +144 -0
  154. data/spec/logger/writers/file_writer_spec.rb +37 -0
  155. data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
  156. data/spec/logger/writers/stream_writer_spec.rb +38 -0
  157. data/spec/presenters/base_dsl_spec.rb +111 -0
  158. data/spec/presenters/base_spec.rb +871 -0
  159. data/spec/presenters/common_resource_fields_spec.rb +30 -0
  160. data/spec/presenters/embedding_spec.rb +87 -0
  161. data/spec/presenters/types/array_spec.rb +249 -0
  162. data/spec/presenters/types/boolean_spec.rb +51 -0
  163. data/spec/presenters/types/date_spec.rb +57 -0
  164. data/spec/presenters/types/date_time_spec.rb +59 -0
  165. data/spec/presenters/types/decimal_spec.rb +58 -0
  166. data/spec/presenters/types/enum_spec.rb +71 -0
  167. data/spec/presenters/types/field_spec.rb +77 -0
  168. data/spec/presenters/types/float_spec.rb +50 -0
  169. data/spec/presenters/types/hash_spec.rb +1069 -0
  170. data/spec/presenters/types/integer_spec.rb +50 -0
  171. data/spec/presenters/types/object_spec.rb +177 -0
  172. data/spec/presenters/types/string_spec.rb +65 -0
  173. data/spec/presenters/types/tags_spec.rb +56 -0
  174. data/spec/presenters/types/text_spec.rb +50 -0
  175. data/spec/presenters/types/uuid_spec.rb +46 -0
  176. data/spec/presenters/walk_spec.rb +198 -0
  177. data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
  178. data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
  179. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
  180. data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
  181. data/spec/services/discovery/discovery_spec.rb +73 -0
  182. data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
  183. data/spec/services/discovery/results/for_http_spec.rb +37 -0
  184. data/spec/services/discovery/results/for_local_spec.rb +21 -0
  185. data/spec/services/discovery/results/for_remote_spec.rb +15 -0
  186. data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
  187. data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
  188. data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
  189. data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
  190. data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
  191. data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
  192. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
  193. data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
  194. data/spec/services/middleware/middleware_cors_spec.rb +93 -0
  195. data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
  196. data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
  197. data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
  198. data/spec/services/middleware/middleware_logging_spec.rb +356 -0
  199. data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
  200. data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
  201. data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
  202. data/spec/services/middleware/middleware_public_spec.rb +238 -0
  203. data/spec/services/middleware/middleware_spec.rb +1569 -0
  204. data/spec/services/middleware/string_inquirer_spec.rb +30 -0
  205. data/spec/services/services/application_spec.rb +74 -0
  206. data/spec/services/services/context_spec.rb +48 -0
  207. data/spec/services/services/implementation_spec.rb +45 -0
  208. data/spec/services/services/interface_spec.rb +262 -0
  209. data/spec/services/services/permissions_spec.rb +249 -0
  210. data/spec/services/services/request_spec.rb +95 -0
  211. data/spec/services/services/response_spec.rb +250 -0
  212. data/spec/services/services/session_spec.rb +432 -0
  213. data/spec/spec_helper.rb +298 -0
  214. data/spec/utilities/utilities_spec.rb +537 -0
  215. data/spec/utilities/uuid_spec.rb +20 -0
  216. metadata +615 -0
@@ -0,0 +1,238 @@
1
+ # service_middleware_spec.rb is already large, so split out tests related
2
+ # explicitly to public actions in interfaces here.
3
+
4
+ require 'spec_helper'
5
+
6
+ # Create three service applications. The first has one interface with no public
7
+ # actions. The second has one interface with a public and private action. The
8
+ # last contains both interfaces.
9
+
10
+ class TestNoPublicActionImplementation < Hoodoo::Services::Implementation
11
+ def list( context )
12
+ context.response.set_resources( [ { 'private' => true } ] )
13
+ end
14
+
15
+ def show( context )
16
+ context.response.set_resources( { 'private' => true } )
17
+ end
18
+ end
19
+
20
+ class TestPublicActionImplementation < Hoodoo::Services::Implementation
21
+ def list( context )
22
+ context.response.set_resources( [ { 'public' => true } ] )
23
+ end
24
+
25
+ def show( context )
26
+ context.response.set_resources( { 'public' => true } )
27
+ end
28
+ end
29
+
30
+ class TestNoPublicActionInterface < Hoodoo::Services::Interface
31
+ interface :NoPublicAction do
32
+ endpoint :no_public_action, TestNoPublicActionImplementation
33
+ actions :show, :list
34
+ end
35
+ end
36
+
37
+ class TestPublicActionInterface < Hoodoo::Services::Interface
38
+ interface :PublicAction do
39
+ endpoint :public_action, TestPublicActionImplementation
40
+ actions :show, :list
41
+ public_actions :list
42
+ end
43
+ end
44
+
45
+ class TestNoPublicActionService < Hoodoo::Services::Service
46
+ comprised_of TestNoPublicActionInterface
47
+ end
48
+
49
+ class TestPublicActionService < Hoodoo::Services::Service
50
+ comprised_of TestPublicActionInterface
51
+ end
52
+
53
+ class TestMixPublicActionService < Hoodoo::Services::Service
54
+ comprised_of TestNoPublicActionInterface,
55
+ TestPublicActionInterface
56
+ end
57
+
58
+ # Run the tests
59
+
60
+ describe Hoodoo::Services::Middleware do
61
+
62
+ def try_to_call( endpoint, ident = nil )
63
+ get(
64
+ "/v1/#{ endpoint }/#{ ident }",
65
+ nil,
66
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
67
+ )
68
+ end
69
+
70
+ before :example do
71
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
72
+ Hoodoo::Services::Middleware.set_test_session( Hoodoo::Services::Middleware::DEFAULT_TEST_SESSION )
73
+ end
74
+
75
+ before :example, :without_session => true do
76
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
77
+ Hoodoo::Services::Middleware.set_test_session( nil )
78
+ end
79
+
80
+ after :example do
81
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
82
+ end
83
+
84
+ # -------------------------------------------------------------------------
85
+
86
+ context 'with only secure actions' do
87
+
88
+ # Middleware maintains class-level record of whether or not any interfaces
89
+ # had public actions for efficiency; in case any other test dirtied this by
90
+ # accident, clean out the record here.
91
+ #
92
+ before :all do
93
+ Hoodoo::Services::Middleware.class_variable_set( '@@interfaces_have_public_methods', false )
94
+ end
95
+
96
+ def app
97
+ Rack::Builder.new do
98
+ use Hoodoo::Services::Middleware
99
+ run TestNoPublicActionService.new
100
+ end
101
+ end
102
+
103
+ context '#list' do
104
+ it 'prohibits actions without session', :without_session => true do
105
+ try_to_call( 'no_public_action' )
106
+ expect( last_response.status ).to eq( 401 )
107
+ end
108
+
109
+ it 'allows actions with session' do
110
+ try_to_call( 'no_public_action' )
111
+ expect( last_response.status ).to eq( 200 )
112
+ end
113
+ end
114
+
115
+ context '#show' do
116
+ it 'prohibits actions without session', :without_session => true do
117
+ try_to_call( 'no_public_action', 'some_uuid' )
118
+ expect( last_response.status ).to eq( 401 )
119
+ end
120
+
121
+ it 'allows actions with session' do
122
+ try_to_call( 'no_public_action', 'some_uuid' )
123
+ expect( last_response.status ).to eq( 200 )
124
+ end
125
+ end
126
+ end
127
+
128
+ # -------------------------------------------------------------------------
129
+
130
+ context 'with a public action' do
131
+
132
+ # Middleware maintains class-level record of whether or not any interfaces
133
+ # had public actions for efficiency; ensure this is cleared after all these
134
+ # tests run, so it's a clean slate for the next set.
135
+ #
136
+ after :all do
137
+ Hoodoo::Services::Middleware::class_variable_set( '@@interfaces_have_public_methods', false )
138
+ end
139
+
140
+ def app
141
+ Rack::Builder.new do
142
+ use Hoodoo::Services::Middleware
143
+ run TestPublicActionService.new
144
+ end
145
+ end
146
+
147
+ it 'prohibits secure actions without session', :without_session => true do
148
+ try_to_call( 'public_action', 'some_uuid' )
149
+ expect( last_response.status ).to eq( 401 )
150
+ end
151
+
152
+ it 'allows secure actions with session' do
153
+ try_to_call( 'public_action', 'some_uuid' )
154
+ expect( last_response.status ).to eq( 200 )
155
+ end
156
+
157
+ it 'allows public actions without session', :without_session => true do
158
+ try_to_call( 'public_action' )
159
+ expect( last_response.status ).to eq( 200 )
160
+ end
161
+
162
+ it 'allows public actions with session' do
163
+ try_to_call( 'public_action' )
164
+ expect( last_response.status ).to eq( 200 )
165
+ end
166
+ end
167
+
168
+ # -------------------------------------------------------------------------
169
+
170
+ context 'with mixed access across interfaces' do
171
+
172
+ # Middleware maintains class-level record of whether or not any interfaces
173
+ # had public actions for efficiency; ensure this is cleared after all these
174
+ # tests run, so it's a clean slate for the next set.
175
+ #
176
+ after :all do
177
+ Hoodoo::Services::Middleware::class_variable_set( '@@interfaces_have_public_methods', false )
178
+ end
179
+
180
+ def app
181
+ Rack::Builder.new do
182
+ use Hoodoo::Services::Middleware
183
+ run TestMixPublicActionService.new
184
+ end
185
+ end
186
+
187
+ context 'in interface with no public actions' do
188
+ context '#list' do
189
+ it 'prohibits actions without session', :without_session => true do
190
+ try_to_call( 'no_public_action' )
191
+ expect( last_response.status ).to eq( 401 )
192
+ end
193
+
194
+ it 'allows actions with session' do
195
+ try_to_call( 'no_public_action' )
196
+ expect( last_response.status ).to eq( 200 )
197
+ end
198
+ end
199
+
200
+ context '#show' do
201
+ it 'prohibits actions without session', :without_session => true do
202
+ try_to_call( 'no_public_action', 'some_uuid' )
203
+ expect( last_response.status ).to eq( 401 )
204
+ end
205
+
206
+ it 'allows actions with session' do
207
+ try_to_call( 'no_public_action', 'some_uuid' )
208
+ expect( last_response.status ).to eq( 200 )
209
+ end
210
+ end
211
+ end
212
+
213
+ context 'in interface with public actions' do
214
+ it 'prohibits secure actions without session', :without_session => true do
215
+ try_to_call( 'public_action', 'some_uuid' )
216
+ expect( last_response.status ).to eq( 401 )
217
+ end
218
+
219
+ it 'allows secure actions with session' do
220
+ try_to_call( 'public_action', 'some_uuid' )
221
+ expect( last_response.status ).to eq( 200 )
222
+ end
223
+
224
+ it 'allows public actions without session', :without_session => true do
225
+ try_to_call( 'public_action' )
226
+ expect( last_response.status ).to eq( 200 )
227
+ end
228
+
229
+ it 'allows public actions with session' do
230
+ try_to_call( 'public_action' )
231
+ expect( last_response.status ).to eq( 200 )
232
+ end
233
+ end
234
+ end
235
+
236
+ # -------------------------------------------------------------------------
237
+
238
+ end
@@ -0,0 +1,1569 @@
1
+ # This unavoidably combines a lot of elements of integration testing since
2
+ # little of the middleware is really usable in isolation. We could test its
3
+ # component methods individually, but the interesting ones are all part of
4
+ # request processing anyway. Code coverage lets us know if we missed any
5
+ # internal methods when testing the request processing flow.
6
+
7
+ require 'spec_helper'
8
+
9
+
10
+ ###############################################################################
11
+ # Single endpoint
12
+ ###############################################################################
13
+
14
+
15
+ class RSpecTestServiceStubImplementation < Hoodoo::Services::Implementation
16
+ end
17
+
18
+ class RSpecTestServiceStubBeforeAfterImplementation < Hoodoo::Services::Implementation
19
+ def before(context)
20
+ end
21
+
22
+ def after(context)
23
+ end
24
+ end
25
+
26
+ class RSpecTestServiceStubInterface < Hoodoo::Services::Interface
27
+ interface :RSpecTestResource do
28
+ version 2
29
+ endpoint :rspec_test_service_stub, RSpecTestServiceStubImplementation
30
+ embeds :emb, :embs
31
+ to_list do
32
+ sort :extra => [:up, :down]
33
+ search :foo, :bar
34
+ filter :baz, :boo
35
+ end
36
+ to_create do
37
+ text :foo, :required => true
38
+ integer :bar
39
+ end
40
+ to_update do
41
+ text :baz
42
+ integer :foo, :required => true
43
+ end
44
+ end
45
+ end
46
+
47
+ class RSpecTestMatchingServiceStubInterface < Hoodoo::Services::Interface
48
+ interface :RSpecTestResource do
49
+ version 2
50
+ endpoint :rspec_test_service_stub, RSpecTestServiceStubImplementation
51
+ end
52
+ end
53
+
54
+ class RSpecTestServiceStubBeforeInterface < Hoodoo::Services::Interface
55
+ interface :RSpecTestResource do
56
+ version 2
57
+ endpoint :rspec_test_service_before_after_stub, RSpecTestServiceStubBeforeAfterImplementation
58
+ embeds :emb, :embs
59
+ to_list do
60
+ sort :extra => [:up, :down]
61
+ search :foo, :bar
62
+ filter :baz, :boo
63
+ end
64
+ end
65
+ end
66
+
67
+ class RSpecTestServiceStub < Hoodoo::Services::Service
68
+ comprised_of RSpecTestServiceStubInterface, RSpecTestServiceStubBeforeInterface
69
+ end
70
+
71
+ describe Hoodoo::Services::Middleware do
72
+
73
+ def app
74
+ Rack::Builder.new do
75
+ use Hoodoo::Services::Middleware
76
+ run RSpecTestServiceStub.new
77
+ end
78
+ end
79
+
80
+ context 'internal sanity checks' do
81
+ it 'should complain about bad instantiation' do
82
+ expect {
83
+ Hoodoo::Services::Middleware.new( {} )
84
+ }.to raise_error(RuntimeError, "Hoodoo::Services::Middleware instance created with non-Service entity of class 'Hash' - is this the last middleware in the chain via 'use()' and is Rack 'run()'-ing the correct thing?")
85
+ end
86
+
87
+ it 'should complain about bad instantiation due to bad NewRelic' do
88
+ expect {
89
+ module NewRelic
90
+ module Agent
91
+ module Instrumentation
92
+ class MiddlewareProxy
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ Hoodoo::Services::Middleware.new( NewRelic::Agent::Instrumentation::MiddlewareProxy.new )
99
+ }.to raise_error(RuntimeError, "Hoodoo::Services::Middleware instance created with NewRelic-wrapped Service entity, but NewRelic API is not as expected by Hoodoo; incompatible NewRelic version.")
100
+
101
+ Object.send( :remove_const, :NewRelic )
102
+ end
103
+
104
+ it 'should complain about bad instantiation via NewRelic' do
105
+ expect {
106
+ module NewRelic
107
+ module Agent
108
+ module Instrumentation
109
+ class MiddlewareProxy
110
+ def target
111
+ {}
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ Hoodoo::Services::Middleware.new( NewRelic::Agent::Instrumentation::MiddlewareProxy.new )
119
+ }.to raise_error(RuntimeError, "Hoodoo::Services::Middleware instance created with non-Service entity of class 'Hash' - is this the last middleware in the chain via 'use()' and is Rack 'run()'-ing the correct thing?")
120
+
121
+ Object.send( :remove_const, :NewRelic )
122
+ end
123
+
124
+ it 'should complain about bad applications directly or via NewRelic' do
125
+ class RSpecTestServiceStubBadInterface < Hoodoo::Services::Interface
126
+ end
127
+ class RSpecTestServiceStubBad < Hoodoo::Services::Service
128
+ comprised_of RSpecTestServiceStubBadInterface
129
+ end
130
+
131
+ expect {
132
+ Hoodoo::Services::Middleware.new( RSpecTestServiceStubBad.new )
133
+ }.to raise_error(RuntimeError, "Hoodoo::Services::Middleware encountered invalid interface class RSpecTestServiceStubBadInterface via service class RSpecTestServiceStubBad")
134
+
135
+ expect {
136
+ module NewRelic
137
+ module Agent
138
+ module Instrumentation
139
+ class MiddlewareProxy
140
+ def target
141
+ RSpecTestServiceStubBad.new
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ Hoodoo::Services::Middleware.new( NewRelic::Agent::Instrumentation::MiddlewareProxy.new )
149
+ }.to raise_error(RuntimeError, "Hoodoo::Services::Middleware encountered invalid interface class RSpecTestServiceStubBadInterface via service class RSpecTestServiceStubBad")
150
+
151
+ Object.send( :remove_const, :NewRelic )
152
+ end
153
+
154
+ it 'should self-check content type' do
155
+ mw = Hoodoo::Services::Middleware.new( RSpecTestServiceStub.new )
156
+ interaction = Hoodoo::Services::Middleware::Interaction.new( {}, mw )
157
+ interaction.requested_content_type = 'application/xml'
158
+ expect {
159
+ mw.send( :parse_body_string_into, interaction, '{}' )
160
+ }.to raise_error(RuntimeError, "Internal error - content type 'application/xml' is not supported here; \#deal_with_content_type_header() should have caught that");
161
+ end
162
+
163
+ it 'should detect a local versus remote endpoint mismatch' do
164
+ mw = Hoodoo::Services::Middleware.new( RSpecTestServiceStub.new )
165
+ interaction = Hoodoo::Services::Middleware::Interaction.new( {}, mw )
166
+
167
+ mock_discoverer = OpenStruct.new
168
+ mw.instance_variable_set( '@discoverer', mock_discoverer )
169
+ expect( mock_discoverer ).to receive( :is_local? ).and_return( true )
170
+
171
+ expect {
172
+ mw.send( :inter_resource_endpoint_for, :NotALocalResource, 1, interaction )
173
+ }.to raise_error(RuntimeError, 'Hoodoo::Services::Middleware#inter_resource_endpoint_for: Internal error - version 1 of resource NotALocalResource endpoint is local according to the discovery engine, but no local service discovery record can be found')
174
+ end
175
+ end
176
+
177
+ context 'utility methods' do
178
+ it 'should know about Memcached via environment variable' do
179
+ old = ENV[ 'MEMCACHED_HOST' ]
180
+ ENV[ 'MEMCACHED_HOST' ] = nil
181
+ expect(Hoodoo::Services::Middleware.has_memcached?).to eq(false)
182
+ ENV[ 'MEMCACHED_HOST' ] = 'foo'
183
+ expect(Hoodoo::Services::Middleware.has_memcached?).to eq(true)
184
+ ENV[ 'MEMCACHED_HOST' ] = old
185
+ end
186
+
187
+ it 'should know about Memcached via legacy environment variable' do
188
+ old = ENV[ 'MEMCACHED_HOST' ]
189
+ ENV[ 'MEMCACHED_HOST' ] = nil
190
+ expect(Hoodoo::Services::Middleware.has_memcached?).to eq(false)
191
+ ENV[ 'MEMCACHED_HOST' ] = 'foo'
192
+ expect(Hoodoo::Services::Middleware.has_memcached?).to eq(true)
193
+ ENV[ 'MEMCACHED_HOST' ] = old
194
+ end
195
+
196
+ it 'should know about a queue' do
197
+ old = ENV[ 'AMQ_ENDPOINT' ]
198
+ ENV[ 'AMQ_ENDPOINT' ] = nil
199
+ expect(Hoodoo::Services::Middleware.on_queue?).to eq(false)
200
+ ENV[ 'AMQ_ENDPOINT' ] = 'foo'
201
+ expect(Hoodoo::Services::Middleware.on_queue?).to eq(true)
202
+ ENV[ 'AMQ_ENDPOINT' ] = old
203
+ end
204
+ end
205
+
206
+ context 'malformed basics in requests' do
207
+
208
+ it 'should complain about entirely missing content type' do
209
+ get '/v2/rspec_test_service_stub'
210
+
211
+ expect(last_response.status).to eq(422)
212
+
213
+ result = JSON.parse(last_response.body)
214
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
215
+ expect(result['errors'][0]['message']).to eq("Content-Type '<unknown>' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
216
+ end
217
+
218
+ it 'should complain about missing charset' do
219
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json' }
220
+
221
+ expect(last_response.status).to eq(422)
222
+
223
+ result = JSON.parse(last_response.body)
224
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
225
+ expect(result['errors'][0]['message']).to eq("Content-Type 'application/json' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
226
+ end
227
+
228
+ it 'should complain about incorrect content type' do
229
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'some/thing; charset=utf-8' }
230
+
231
+ expect(last_response.status).to eq(422)
232
+
233
+ result = JSON.parse(last_response.body)
234
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
235
+ expect(result['errors'][0]['message']).to eq("Content-Type 'some/thing; charset=utf-8' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
236
+ end
237
+
238
+ it 'should complain about incorrect content type' do
239
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=madeup' }
240
+
241
+ expect(last_response.status).to eq(422)
242
+
243
+ result = JSON.parse(last_response.body)
244
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
245
+ expect(result['errors'][0]['message']).to eq("Content-Type 'application/json; charset=madeup' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'")
246
+ end
247
+
248
+ it 'should generate interaction IDs and other standard headers even for error states' do
249
+ get '/v2/rspec_test_service_stub'
250
+
251
+ expect(last_response.status).to eq(422)
252
+ expect(last_response.headers['X-Interaction-ID']).to_not be_nil
253
+ expect(last_response.headers['X-Interaction-ID'].size).to eq(32)
254
+ expect(last_response.headers['Content-Type']).to eq('application/json; charset=utf-8')
255
+ end
256
+
257
+ end
258
+
259
+ context 'sessions' do
260
+
261
+ # This leans on assumption that Permissions#permitted? has already
262
+ # got adequate test coverage elsewhere, so we just make sure that
263
+ # it seems to allow or deny as expected via the session.
264
+
265
+ context 'present' do
266
+ it 'should check for session permissions' do
267
+ expect_any_instance_of(Hoodoo::Services::Session).to receive(:permissions).at_least( 1 ).times.and_call_original
268
+ expect_any_instance_of(Hoodoo::Services::Permissions).to receive(:permitted?).at_least( 1 ).times.and_call_original
269
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once.and_return([])
270
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
271
+ expect(last_response.status).to eq(200)
272
+ end
273
+ end
274
+
275
+ context 'with restricted permissions' do
276
+ before :example do
277
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
278
+ test_session = @old_test_session.dup
279
+ permissions = Hoodoo::Services::Permissions.new # (this is "default-else-deny")
280
+ permissions.set_resource( :RSpecTestResource, :list, Hoodoo::Services::Permissions::ALLOW )
281
+ permissions.set_resource( :RSpecTestResource, :show, Hoodoo::Services::Permissions::ASK )
282
+ test_session.permissions = permissions
283
+ Hoodoo::Services::Middleware.set_test_session( test_session )
284
+ end
285
+
286
+ after :example do
287
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
288
+ end
289
+
290
+ it 'denies' do
291
+ delete '/v2/rspec_test_service_stub/uuid', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
292
+ expect(last_response.status).to eq(403)
293
+ end
294
+
295
+ it 'allows' do
296
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once.and_return([])
297
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
298
+ expect(last_response.status).to eq(200)
299
+ end
300
+
301
+ it 'asks' do
302
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:verify).once.and_return(Hoodoo::Services::Permissions::ALLOW)
303
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).once.and_return({})
304
+ get '/v2/rspec_test_service_stub/uuid', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
305
+ expect(last_response.status).to eq(200)
306
+ end
307
+ end
308
+
309
+ context 'absent' do
310
+ before :example do
311
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
312
+ Hoodoo::Services::Middleware.set_test_session( nil )
313
+ end
314
+
315
+ after :example do
316
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
317
+ end
318
+
319
+ it 'should check for missing session data' do
320
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
321
+ expect(last_response.status).to eq(401)
322
+ result = JSON.parse(last_response.body)
323
+ expect(result['errors'][0]['code']).to eq('platform.invalid_session')
324
+ end
325
+ end
326
+
327
+ end
328
+
329
+ context 'well formed request for' do
330
+
331
+ it 'no matching endpoint should return 404 with lower case in content type' do
332
+ get '/v2/where_are_you', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
333
+ expect(last_response.status).to eq(404)
334
+ end
335
+
336
+ it 'no matching endpoint should return 404 with mixed case in content type' do
337
+ get '/v2/where_are_you', nil, { 'CONTENT_TYPE' => 'APPLICATION/json; charset=UTF-8' }
338
+ expect(last_response.status).to eq(404)
339
+ end
340
+
341
+ it 'a matching endpoint should use fallback exception handler if early failures occur' do
342
+
343
+ # Stub out anything early in request handling inside call() and make it
344
+ # throw an exception.
345
+
346
+ expect_any_instance_of(Hoodoo::Services::Middleware).to receive(:debug_log).and_raise("boo!")
347
+ expect_any_instance_of(Hoodoo::Services::Middleware).to receive(:record_exception).and_raise("boo!")
348
+
349
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
350
+
351
+ expect(last_response.status).to eq(500)
352
+ expect(last_response.body).to include('Middleware exception in exception handler')
353
+ end
354
+
355
+ it 'a matching endpoint should use fallback exception handler if the primary handler fails' do
356
+
357
+ # This implicitly tests that Hoodoo' exception handler checks for test
358
+ # and development mode and if both are false, calls the exception reporter.
359
+ #
360
+ # So, first, these are part of routine processing.
361
+
362
+ expect(Hoodoo::Services::Middleware.environment).to receive(:test?).exactly(2).times.and_return(true)
363
+
364
+ # The check for 'unless test or development' is made prior to trying to use
365
+ # the ExceptionReporter class, so say 'no' to both then get the reporter to
366
+ # itself raise an error.
367
+
368
+ expect(Hoodoo::Services::Middleware.environment).to receive(:test?).once.and_return(false)
369
+ expect(Hoodoo::Services::Middleware.environment).to receive(:development?).and_return(false)
370
+ expect(Hoodoo::Services::Middleware::ExceptionReporting).to receive(:report).and_raise("boo!")
371
+
372
+ # Route through to the unimplemented "list" call, so the subclass raises
373
+ # an exception. This is tested independently elsewhere too. This causes
374
+ # the normal exception handler to run, which breaks because of the above
375
+ # code, so we get the fallback.
376
+
377
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).and_call_original
378
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
379
+
380
+ expect(last_response.status).to eq(500)
381
+ expect(last_response.body).to eq('Middleware exception in exception handler')
382
+ end
383
+
384
+ # -------------------------------------------------------------------------
385
+
386
+ describe 'service implementation #before and #after' do
387
+ it 'should get called if defined in correct order' do
388
+ expect_any_instance_of(RSpecTestServiceStubBeforeAfterImplementation).to receive(:before).once do | ignored_rspec_mock_instance, context |
389
+ expect(context).to be_a(Hoodoo::Services::Context)
390
+ end
391
+
392
+ expect_any_instance_of(RSpecTestServiceStubBeforeAfterImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
393
+ expect(context).to be_a(Hoodoo::Services::Context)
394
+ end
395
+
396
+ expect_any_instance_of(RSpecTestServiceStubBeforeAfterImplementation).to receive(:after).once do | ignored_rspec_mock_instance, context |
397
+ expect(context).to be_a(Hoodoo::Services::Context)
398
+ end
399
+
400
+ get '/v2/rspec_test_service_before_after_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
401
+ end
402
+
403
+ it 'should not call action if before generates errors' do
404
+ expect_any_instance_of(RSpecTestServiceStubBeforeAfterImplementation).to receive(:before).once do | ignored_rspec_mock_instance, context |
405
+ response.add_error( 'service_calls_a.triggered')
406
+ end
407
+
408
+ expect_any_instance_of(RSpecTestServiceStubBeforeAfterImplementation).not_to receive(:list)
409
+
410
+ get '/v2/rspec_test_service_before_after_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
411
+ end
412
+ end
413
+
414
+ # -------------------------------------------------------------------------
415
+
416
+ describe 'service implementation #list' do
417
+ it 'should get called with default values' do
418
+
419
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
420
+ expect(context).to be_a(Hoodoo::Services::Context)
421
+
422
+ session = context.session
423
+ request = context.request
424
+ response = context.response
425
+
426
+ expect(session).to be_a(Hoodoo::Services::Session)
427
+ expect(request).to be_a(Hoodoo::Services::Request)
428
+ expect(response).to be_a(Hoodoo::Services::Response)
429
+
430
+ expect(request.locale).to eq('en-nz')
431
+ expect(request.uri_path_components).to be_empty
432
+ expect(request.ident).to be_nil
433
+ expect(request.uri_path_extension).to eq('')
434
+ expect(request.list.offset).to eq(0)
435
+ expect(request.list.limit).to eq(50)
436
+ expect(request.list.sort_data).to eq({'created_at'=>'desc'})
437
+ expect(request.list.search_data).to eq({})
438
+ expect(request.list.filter_data).to eq({})
439
+ expect(request.embeds).to eq([])
440
+ expect(request.references).to eq([])
441
+ end
442
+
443
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
444
+ expect(last_response.status).to eq(200)
445
+ end
446
+
447
+ it 'should pass on locale correctly (1)' do
448
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
449
+ expect(context.request.locale).to eq('en-gb')
450
+ end
451
+
452
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8',
453
+ 'HTTP_CONTENT_LANGUAGE' => 'EN-GB' }
454
+ expect(last_response.status).to eq(200)
455
+ end
456
+
457
+ it 'should pass on locale correctly (2)' do
458
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
459
+ expect(context.request.locale).to eq('en-gb')
460
+ end
461
+
462
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8',
463
+ 'HTTP_ACCEPT_LANGUAGE' => 'en-GB;q=0.8, en;q=0.7' }
464
+ expect(last_response.status).to eq(200)
465
+ end
466
+
467
+ it 'should get called on varied path forms (1)' do
468
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list)
469
+ get '/v2/rspec_test_service_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
470
+ expect(last_response.status).to eq(200)
471
+ end
472
+
473
+ it 'should get called on varied path forms (2)' do
474
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list)
475
+ get '/v2/rspec_test_service_stub.json', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
476
+ expect(last_response.status).to eq(200)
477
+ end
478
+
479
+ context 'without ActiveRecord' do
480
+ before :each do
481
+ @ar = ActiveRecord
482
+ Object.send( :remove_const, :ActiveRecord )
483
+ end
484
+
485
+ after :each do
486
+ ActiveRecord = @ar
487
+ end
488
+
489
+ it 'still calls the service' do
490
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list)
491
+ get '/v2/rspec_test_service_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
492
+ expect(last_response.status).to eq(200)
493
+ end
494
+ end
495
+
496
+ # We allow this odd form because if it were to be considered 'show', then
497
+ # it'd be show with no path components and a JSON format request. That
498
+ # makes no sense. So it drops out logically as 'list'.
499
+ #
500
+ it 'should get called on varied path forms (3)' do
501
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list)
502
+ get '/v2/rspec_test_service_stub/.json', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
503
+ expect(last_response.status).to eq(200)
504
+ end
505
+
506
+ it 'should complain if the subclass omits the implementation' do
507
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).and_call_original
508
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
509
+ expect(last_response.status).to eq(500)
510
+ result = JSON.parse(last_response.body)
511
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Implementation subclasses must implement 'list'")
512
+ end
513
+
514
+ it 'should complain if any body data is given' do
515
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
516
+ get '/v2/rspec_test_service_stub/', "{}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
517
+ expect(last_response.status).to eq(422)
518
+ result = JSON.parse(last_response.body)
519
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
520
+ end
521
+
522
+ it 'should complain about prohibited query entries' do
523
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
524
+ get '/v2/rspec_test_service_stub?imaginary=42', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
525
+ expect(last_response.status).to eq(422)
526
+ result = JSON.parse(last_response.body)
527
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
528
+ end
529
+
530
+ it 'should respond to limit query parameter' do
531
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
532
+ expect(context.request.list.offset).to eq(0)
533
+ expect(context.request.list.limit).to eq(42)
534
+ end
535
+
536
+ get '/v2/rspec_test_service_stub?limit=42', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
537
+ expect(last_response.status).to eq(200)
538
+ end
539
+
540
+ it 'should take the last limit query parameter if several are given' do
541
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
542
+ expect(context.request.list.offset).to eq(0)
543
+ expect(context.request.list.limit).to eq(9)
544
+ end
545
+
546
+ get '/v2/rspec_test_service_stub?limit=15&limit=42&limit=9', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
547
+ expect(last_response.status).to eq(200)
548
+ end
549
+
550
+ def test_with_limit( limit )
551
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
552
+ get "/v2/rspec_test_service_stub?limit=#{ limit }", nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
553
+ expect(last_response.status).to eq(422)
554
+ result = JSON.parse(last_response.body)
555
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
556
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
557
+ expect(result['errors'][0]['reference']).to eq('limit')
558
+ end
559
+
560
+ it 'should complain about non-numeric limit query parameter' do
561
+ test_with_limit( 'foo' )
562
+ end
563
+
564
+ it 'should complain about zero limit query parameter' do
565
+ test_with_limit( 0 )
566
+ end
567
+
568
+ it 'should complain about negative limit query parameter' do
569
+ test_with_limit( -1 )
570
+ end
571
+
572
+ it 'should respond to offset query parameter' do
573
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
574
+ expect(context.request.list.offset).to eq(42)
575
+ expect(context.request.list.limit).to eq(50)
576
+ end
577
+
578
+ get '/v2/rspec_test_service_stub?offset=42', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
579
+ expect(last_response.status).to eq(200)
580
+ end
581
+
582
+ it 'should take the last offset query parameter if several are given' do
583
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
584
+ expect(context.request.list.offset).to eq(24)
585
+ expect(context.request.list.limit).to eq(50)
586
+ end
587
+
588
+ get '/v2/rspec_test_service_stub?offset=4&offset=42&offset=24', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
589
+ expect(last_response.status).to eq(200)
590
+ end
591
+
592
+ def test_with_offset( offset )
593
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
594
+ get "/v2/rspec_test_service_stub?offset=#{ offset }", nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
595
+ expect(last_response.status).to eq(422)
596
+ result = JSON.parse(last_response.body)
597
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
598
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
599
+ expect(result['errors'][0]['reference']).to eq('offset')
600
+ end
601
+
602
+ it 'should complain about non-numeric offset query parameter' do
603
+ test_with_offset( 'foo' )
604
+ end
605
+
606
+ it 'should complain about negative offset query parameter' do
607
+ test_with_offset( -1 )
608
+ end
609
+
610
+ it 'should take the last offset and last query parameter if several are given' do
611
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
612
+ expect(context.request.list.offset).to eq(24)
613
+ expect(context.request.list.limit).to eq(23)
614
+ end
615
+
616
+ get '/v2/rspec_test_service_stub?limit=2&offset=42&limit=14&limit=23&offset=24', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
617
+ expect(last_response.status).to eq(200)
618
+ end
619
+
620
+ it 'should respond to sort query parameter' do
621
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
622
+ expect(context.request.list.sort_data).to eq({'extra'=>'up'})
623
+ end
624
+
625
+ get '/v2/rspec_test_service_stub?sort=extra', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
626
+ expect(last_response.status).to eq(200)
627
+ end
628
+
629
+ it 'should respond to several sort query parameters (form 1)' do
630
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
631
+ expect(context.request.list.sort_data).to eq({'extra'=>'up', 'created_at'=>'desc'})
632
+ end
633
+
634
+ get '/v2/rspec_test_service_stub?sort=extra,created_at&direction=up,desc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
635
+ expect(last_response.status).to eq(200)
636
+ end
637
+
638
+ it 'should respond to several sort query parameters (form 2)' do
639
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
640
+ expect(context.request.list.sort_data).to eq({'extra'=>'up', 'created_at'=>'desc'})
641
+ end
642
+
643
+ get '/v2/rspec_test_service_stub?sort=extra&sort=created_at&direction=up,desc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
644
+ expect(last_response.status).to eq(200)
645
+ end
646
+
647
+ it 'should respond to several sort query parameters, with duplicates' do
648
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
649
+ expect(context.request.list.sort_data).to eq({'created_at'=>'desc', 'extra'=>'up'})
650
+ end
651
+
652
+ get '/v2/rspec_test_service_stub?sort=extra,extra&sort=created_at&sort=extra&direction=up,desc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
653
+ expect(last_response.status).to eq(200)
654
+ end
655
+
656
+ it 'should complain about bad sort query parameter' do
657
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
658
+ get '/v2/rspec_test_service_stub?sort=foo', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
659
+ expect(last_response.status).to eq(422)
660
+ result = JSON.parse(last_response.body)
661
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
662
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
663
+ expect(result['errors'][0]['reference']).to eq('sort')
664
+ end
665
+
666
+ it 'should respond to direction query parameter, with infered sort' do
667
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
668
+ expect(context.request.list.sort_data).to eq({'created_at'=>'asc'})
669
+ end
670
+
671
+ get '/v2/rspec_test_service_stub?direction=asc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
672
+ expect(last_response.status).to eq(200)
673
+ end
674
+
675
+ it 'should respond to direction query parameter, with explicit sort' do
676
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
677
+ expect(context.request.list.sort_data).to eq({'extra'=>'down'})
678
+ end
679
+
680
+ get '/v2/rspec_test_service_stub?sort=extra&direction=down', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
681
+ expect(last_response.status).to eq(200)
682
+ end
683
+
684
+ it 'should respond to multiple direction query parameters (form 1)' do
685
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
686
+ expect(context.request.list.sort_data).to eq({'extra'=>'down', 'created_at'=>'asc'})
687
+ end
688
+
689
+ get '/v2/rspec_test_service_stub?sort=extra,created_at&direction=down,asc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
690
+ expect(last_response.status).to eq(200)
691
+ end
692
+
693
+ it 'should respond to multiple direction query parameters (form 2)' do
694
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
695
+ expect(context.request.list.sort_data).to eq({'extra'=>'down', 'created_at'=>'asc'})
696
+ end
697
+
698
+ get '/v2/rspec_test_service_stub?direction=down&sort=extra,created_at&direction=asc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
699
+ expect(last_response.status).to eq(200)
700
+ end
701
+
702
+ # When there's more than one sort parameter, we need a matching number
703
+ # of sort and direction keys. Use valid sort keys and directions so we
704
+ # know that the "platform.malformed" the test expects is actually
705
+ # coming from the count mismatch, not because the parameters are not
706
+ # recognised sort keys or dierctions.
707
+ #
708
+ it 'should complain about too many direction query parameters' do
709
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
710
+ get '/v2/rspec_test_service_stub?sort=created_at&direction=desc,down', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
711
+ expect(last_response.status).to eq(422)
712
+ result = JSON.parse(last_response.body)
713
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
714
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
715
+ expect(result['errors'][0]['reference']).to eq('direction')
716
+ end
717
+
718
+ it 'should complain about too many sort query parameters' do
719
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
720
+ get '/v2/rspec_test_service_stub?sort=created_at,extra&direction=desc', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
721
+ expect(last_response.status).to eq(422)
722
+ result = JSON.parse(last_response.body)
723
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
724
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
725
+ expect(result['errors'][0]['reference']).to eq('direction')
726
+ end
727
+
728
+ it 'should complain about bad direction query parameter' do
729
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
730
+ get '/v2/rspec_test_service_stub?sort=created_at&direction=foo', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
731
+ expect(last_response.status).to eq(422)
732
+ result = JSON.parse(last_response.body)
733
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
734
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
735
+ expect(result['errors'][0]['reference']).to eq('direction')
736
+ end
737
+
738
+ it 'should respond to search query parameter (form 1)' do
739
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
740
+ expect(context.request.list.search_data).to eq({'foo' => 'val', 'bar' => 'more'})
741
+ end
742
+
743
+ get '/v2/rspec_test_service_stub?search=foo%3Dval%26bar%3Dmore', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
744
+ expect(last_response.status).to eq(200)
745
+ end
746
+
747
+ it 'should respond to search query parameter (form 2)' do
748
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
749
+ expect(context.request.list.search_data).to eq({'foo' => 'val', 'bar' => 'more'})
750
+ end
751
+
752
+ get '/v2/rspec_test_service_stub?search=foo%3Dval&search=bar%3Dmore', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
753
+ expect(last_response.status).to eq(200)
754
+ end
755
+
756
+ it 'should respond to search query parameter, resolving duplicates' do
757
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
758
+ expect(context.request.list.search_data).to eq({'foo' => 'override', 'bar' => 'more'})
759
+ end
760
+
761
+ get '/v2/rspec_test_service_stub?search=foo%3Dval&search=bar%3Dmore%26foo%3Doverride', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
762
+ expect(last_response.status).to eq(200)
763
+ end
764
+
765
+ it 'should complain about bad search query parameter (form 1)' do
766
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
767
+ get '/v2/rspec_test_service_stub?search=thing%3Dval%26thang%3Dval', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
768
+ expect(last_response.status).to eq(422)
769
+ result = JSON.parse(last_response.body)
770
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
771
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
772
+ expect(result['errors'][0]['reference']).to eq('search: thing\\, thang')
773
+ end
774
+
775
+ it 'should complain about bad search query parameter (form 2)' do
776
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
777
+ get '/v2/rspec_test_service_stub?search=thing%3Dval&search=thang%3Dval', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
778
+ expect(last_response.status).to eq(422)
779
+ result = JSON.parse(last_response.body)
780
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
781
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
782
+ expect(result['errors'][0]['reference']).to eq('search: thing\\, thang')
783
+ end
784
+
785
+ it 'should respond to filter query parameter (form 1)' do
786
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
787
+ expect(context.request.list.filter_data).to eq({'baz' => 'more', 'boo' => 'val'})
788
+ end
789
+
790
+ get '/v2/rspec_test_service_stub?filter=boo%3Dval%26baz%3Dmore', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
791
+ expect(last_response.status).to eq(200)
792
+ end
793
+
794
+ it 'should respond to filter query parameter (form 2)' do
795
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
796
+ expect(context.request.list.filter_data).to eq({'baz' => 'more', 'boo' => 'val'})
797
+ end
798
+
799
+ get '/v2/rspec_test_service_stub?filter=boo%3Dval&filter=baz%3Dmore', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
800
+ expect(last_response.status).to eq(200)
801
+ end
802
+
803
+ it 'should respond to filter query parameter, resolving duplicates' do
804
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
805
+ expect(context.request.list.filter_data).to eq({'boo' => 'override1', 'baz' => 'override2'})
806
+ end
807
+
808
+ get '/v2/rspec_test_service_stub?filter=boo%3Dval&filter=baz%3Dmore&filter=baz%3Doverride2%26boo%3Doverride1', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
809
+ expect(last_response.status).to eq(200)
810
+ end
811
+
812
+ it 'should complain about bad filter query parameter (form 1)' do
813
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
814
+ get '/v2/rspec_test_service_stub?filter=thung%3Dval%26theng%3Dval', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
815
+ expect(last_response.status).to eq(422)
816
+ result = JSON.parse(last_response.body)
817
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
818
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
819
+ expect(result['errors'][0]['reference']).to eq('filter: thung\\, theng')
820
+ end
821
+
822
+ it 'should complain about bad search and filter query parameter' do
823
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
824
+ get '/v2/rspec_test_service_stub?search=thung%3Dval&filter=theng%3Dval', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
825
+ expect(last_response.status).to eq(422)
826
+ result = JSON.parse(last_response.body)
827
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
828
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
829
+ expect(result['errors'][0]['reference']).to eq('search: thung\\, filter: theng')
830
+ end
831
+
832
+ it 'should complain about bad filter query parameter (form 2)' do
833
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
834
+ get '/v2/rspec_test_service_stub?filter=thung%3Dval&filter=theng%3Dval', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
835
+ expect(last_response.status).to eq(422)
836
+ result = JSON.parse(last_response.body)
837
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
838
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
839
+ expect(result['errors'][0]['reference']).to eq('filter: thung\\, theng')
840
+ end
841
+
842
+ it 'should respond to embed query parameter' do
843
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
844
+ expect(context.request.embeds).to eq(['embs', 'emb'])
845
+ end
846
+
847
+ get '/v2/rspec_test_service_stub?_embed=embs,emb', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
848
+ expect(last_response.status).to eq(200)
849
+ end
850
+
851
+ it 'should filter out duplicates in embed query parameter' do
852
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
853
+ expect(context.request.embeds).to eq(['embs', 'emb'])
854
+ end
855
+
856
+ get '/v2/rspec_test_service_stub?_embed=embs,emb,embs,embs,emb,emb,embs', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
857
+ expect(last_response.status).to eq(200)
858
+ end
859
+
860
+ it 'should complain about bad embed query parameter' do
861
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
862
+ get '/v2/rspec_test_service_stub?_embed=one,emb,two', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
863
+ expect(last_response.status).to eq(422)
864
+ result = JSON.parse(last_response.body)
865
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
866
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
867
+ expect(result['errors'][0]['reference']).to eq('_embed: one\\, two')
868
+ end
869
+
870
+ it 'should respond to reference query parameter' do
871
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
872
+ expect(context.request.references).to eq(['embs', 'emb'])
873
+ end
874
+
875
+ get '/v2/rspec_test_service_stub?_reference=embs,emb', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
876
+ expect(last_response.status).to eq(200)
877
+ end
878
+
879
+ it 'should filter out duplicates in reference query parameter' do
880
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
881
+ expect(context.request.references).to eq(['embs', 'emb'])
882
+ end
883
+
884
+ get '/v2/rspec_test_service_stub?_reference=embs,emb,embs,embs,emb,emb,embs', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
885
+ expect(last_response.status).to eq(200)
886
+ end
887
+
888
+ it 'should complain about bad reference query parameter' do
889
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
890
+ get '/v2/rspec_test_service_stub?_reference=one,emb,two', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
891
+ expect(last_response.status).to eq(422)
892
+ result = JSON.parse(last_response.body)
893
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
894
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
895
+ expect(result['errors'][0]['reference']).to eq('_reference: one\\, two')
896
+ end
897
+
898
+ it 'should complain about several bad query parameters' do
899
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
900
+ get '/v2/rspec_test_service_stub?_reference=one,emb,two&direction=asc&sort=foo', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
901
+ expect(last_response.status).to eq(422)
902
+ result = JSON.parse(last_response.body)
903
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
904
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
905
+ expect(result['errors'][0]['reference']).to eq('sort\\, _reference: one\\, two')
906
+ end
907
+
908
+ end
909
+
910
+ # -------------------------------------------------------------------------
911
+
912
+ describe 'service implementation #show' do
913
+ it 'should get called with correct path data (1)' do
914
+
915
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).once do | ignored_rspec_mock_instance, context |
916
+ expect(context.request.uri_path_components).to eq(['12345'])
917
+ expect(context.request.ident).to eq('12345')
918
+ expect(context.request.uri_path_extension).to eq('tar.gz')
919
+ end
920
+
921
+ get '/v2/rspec_test_service_stub/12345.tar.gz', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
922
+ expect(last_response.status).to eq(200)
923
+ end
924
+
925
+ it 'should get called with correct path data (2)' do
926
+
927
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).once do | ignored_rspec_mock_instance, context |
928
+ expect(context.request.uri_path_components).to eq(['12345', '67890'])
929
+ expect(context.request.ident).to eq('12345')
930
+ expect(context.request.uri_path_extension).to eq('json')
931
+ end
932
+
933
+ get '/v2/rspec_test_service_stub/12345/67890.json', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
934
+ expect(last_response.status).to eq(200)
935
+ end
936
+
937
+ it 'should get called with correct path data (3)' do
938
+
939
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).once do | ignored_rspec_mock_instance, context |
940
+ expect(context.request.uri_path_components).to eq(['12345abc'])
941
+ expect(context.request.ident).to eq('12345abc')
942
+ expect(context.request.uri_path_extension).to eq('')
943
+ end
944
+
945
+ get '/v2/rspec_test_service_stub/12345abc/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
946
+ expect(last_response.status).to eq(200)
947
+ end
948
+
949
+ it 'should complain if the subclass omits the implementation' do
950
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).and_call_original
951
+ get '/v2/rspec_test_service_stub/12345.tar.gz', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
952
+ expect(last_response.status).to eq(500)
953
+ result = JSON.parse(last_response.body)
954
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Implementation subclasses must implement 'show'")
955
+ end
956
+
957
+ it 'should complain if any body data is given' do
958
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:show)
959
+ get '/v2/rspec_test_service_stub/12345.tar.gz', "{}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
960
+ expect(last_response.status).to eq(422)
961
+ result = JSON.parse(last_response.body)
962
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
963
+ end
964
+
965
+ it 'should complain about prohibited query entries (1)' do
966
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:show)
967
+ get '/v2/rspec_test_service_stub/12345?limit=25', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
968
+ expect(last_response.status).to eq(422)
969
+ result = JSON.parse(last_response.body)
970
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
971
+ end
972
+
973
+ it 'should complain about prohibited query entries (2)' do
974
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:show)
975
+ get '/v2/rspec_test_service_stub/12345?imaginary=25', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
976
+ expect(last_response.status).to eq(422)
977
+ result = JSON.parse(last_response.body)
978
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
979
+ end
980
+
981
+ it 'should respond to embed query parameter' do
982
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).once do | ignored_rspec_mock_instance, context |
983
+ expect(context.request.embeds).to eq(['embs', 'emb'])
984
+ end
985
+
986
+ get '/v2/rspec_test_service_stub/12345?_embed=embs,emb', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
987
+ expect(last_response.status).to eq(200)
988
+ end
989
+
990
+ it 'should complain about bad embed query parameter' do
991
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:show)
992
+ get '/v2/rspec_test_service_stub/12345?_embed=one,emb,two', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
993
+ expect(last_response.status).to eq(422)
994
+ result = JSON.parse(last_response.body)
995
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
996
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
997
+ expect(result['errors'][0]['reference']).to eq('_embed: one\\, two')
998
+ end
999
+
1000
+ it 'should respond to reference query parameter' do
1001
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:show).once do | ignored_rspec_mock_instance, context |
1002
+ expect(context.request.references).to eq(['embs', 'emb'])
1003
+ end
1004
+
1005
+ get '/v2/rspec_test_service_stub/12345?_reference=embs,emb', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1006
+ expect(last_response.status).to eq(200)
1007
+ end
1008
+
1009
+ it 'should complain about bad reference query parameter' do
1010
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:show)
1011
+ get '/v2/rspec_test_service_stub/12345?_reference=one,emb,two', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1012
+ expect(last_response.status).to eq(422)
1013
+ result = JSON.parse(last_response.body)
1014
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1015
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1016
+ expect(result['errors'][0]['reference']).to eq('_reference: one\\, two')
1017
+ end
1018
+ end
1019
+
1020
+ # -------------------------------------------------------------------------
1021
+
1022
+ describe 'service implementation #create' do
1023
+ it 'should complain if the payload is missing' do
1024
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1025
+ post '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1026
+ expect(last_response.status).to eq(422)
1027
+ result = JSON.parse(last_response.body)
1028
+ expect(result['errors'][0]['code']).to eq('generic.malformed')
1029
+ end
1030
+
1031
+ it 'should complain if the payload is invalid JSON' do
1032
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1033
+ post '/v2/rspec_test_service_stub', "oiushdfoisuhdf", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1034
+ expect(last_response.status).to eq(422)
1035
+ result = JSON.parse(last_response.body)
1036
+ expect(result['errors'][0]['code']).to eq('generic.malformed')
1037
+ end
1038
+
1039
+ it 'should complain if the payload is too large' do
1040
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1041
+ post '/v2/rspec_test_service_stub', "{\"foo\": \"#{'*' * Hoodoo::Services::Middleware::MAXIMUM_PAYLOAD_SIZE }\"}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1042
+ expect(last_response.status).to eq(422)
1043
+ result = JSON.parse(last_response.body)
1044
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1045
+ end
1046
+
1047
+ it 'should complain about incorrect to-create data' do
1048
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1049
+ post '/v2/rspec_test_service_stub', '{ "bar": "not-an-int" }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1050
+ expect(last_response.status).to eq(422)
1051
+ result = JSON.parse(last_response.body)
1052
+ expect(result['errors'].size).to eq(2)
1053
+ expect(result['errors'][0]['message']).to eq('Field `foo` is required')
1054
+ expect(result['errors'][1]['message']).to eq('Field `bar` is an invalid integer')
1055
+ end
1056
+
1057
+ it 'should be happy with valid JSON' do
1058
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create)
1059
+ post '/v2/rspec_test_service_stub', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1060
+ expect(last_response.status).to eq(200)
1061
+ end
1062
+
1063
+ it 'should be happy with no JSON and no to-create verification' do
1064
+ old = RSpecTestServiceStubInterface.to_create
1065
+ RSpecTestServiceStubInterface.send(:to_create=, nil)
1066
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create)
1067
+ post '/v2/rspec_test_service_stub', '{}', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1068
+ expect(last_response.status).to eq(200)
1069
+ RSpecTestServiceStubInterface.send(:to_create=, old)
1070
+ end
1071
+
1072
+ it 'should pass the JSON through' do
1073
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create).once do | ignored_rspec_mock_instance, context |
1074
+ expect(context.request.body).to eq({'foo' => 'present', 'bar' => 42})
1075
+ end
1076
+
1077
+ post '/v2/rspec_test_service_stub', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1078
+ expect(last_response.status).to eq(200)
1079
+ end
1080
+
1081
+ it 'should complain if there is irrelevant path data' do
1082
+ post '/v2/rspec_test_service_stub/12345', "{}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1083
+ expect(last_response.status).to eq(422)
1084
+ end
1085
+
1086
+ it 'should complain if the subclass omits the implementation' do
1087
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create).once.and_call_original
1088
+ post '/v2/rspec_test_service_stub/', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1089
+ expect(last_response.status).to eq(500)
1090
+ result = JSON.parse(last_response.body)
1091
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Implementation subclasses must implement 'create'")
1092
+ end
1093
+
1094
+ it 'should complain about prohibited query entries (1)' do
1095
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1096
+ post '/v2/rspec_test_service_stub?limit=25', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1097
+ expect(last_response.status).to eq(422)
1098
+ result = JSON.parse(last_response.body)
1099
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1100
+ end
1101
+
1102
+ it 'should complain about prohibited query entries (2)' do
1103
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1104
+ post '/v2/rspec_test_service_stub?imaginary=25', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1105
+ expect(last_response.status).to eq(422)
1106
+ result = JSON.parse(last_response.body)
1107
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1108
+ end
1109
+
1110
+ it 'should respond to embed query parameter' do
1111
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create).once do | ignored_rspec_mock_instance, context |
1112
+ expect(context.request.embeds).to eq(['embs', 'emb'])
1113
+ end
1114
+
1115
+ post '/v2/rspec_test_service_stub?_embed=embs,emb', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1116
+ expect(last_response.status).to eq(200)
1117
+ end
1118
+
1119
+ it 'should complain about bad embed query parameter' do
1120
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1121
+ post '/v2/rspec_test_service_stub?_embed=one,emb,two', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1122
+ expect(last_response.status).to eq(422)
1123
+ result = JSON.parse(last_response.body)
1124
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1125
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1126
+ expect(result['errors'][0]['reference']).to eq('_embed: one\\, two')
1127
+ end
1128
+
1129
+ it 'should respond to reference query parameter' do
1130
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create).once do | ignored_rspec_mock_instance, context |
1131
+ expect(context.request.references).to eq(['embs', 'emb'])
1132
+ end
1133
+
1134
+ post '/v2/rspec_test_service_stub?_reference=embs,emb', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1135
+ expect(last_response.status).to eq(200)
1136
+ end
1137
+
1138
+ it 'should complain about bad reference query parameter' do
1139
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:create)
1140
+ post '/v2/rspec_test_service_stub?_reference=one,emb,two', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1141
+ expect(last_response.status).to eq(422)
1142
+ result = JSON.parse(last_response.body)
1143
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1144
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1145
+ expect(result['errors'][0]['reference']).to eq('_reference: one\\, two')
1146
+ end
1147
+
1148
+ context 'with X-Deja-Vu' do
1149
+ before(:each) do
1150
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:create).once do | ignored_rspec_mock_instance, context |
1151
+ context.response.add_error(
1152
+ 'generic.invalid_duplication',
1153
+ {
1154
+ :message => 'testing duplication',
1155
+ :reference => { :field_name => 'test_field' }
1156
+ }
1157
+ )
1158
+ end
1159
+ end
1160
+
1161
+ it 'handles "yes"' do
1162
+ post '/v2/rspec_test_service_stub', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8',
1163
+ 'HTTP_X_DEJA_VU' => 'yes' }
1164
+
1165
+ expect(last_response.status).to eq(204)
1166
+ expect(last_response.body).to be_empty
1167
+ expect(last_response.headers['X-Deja-Vu']).to eq('confirmed')
1168
+ end
1169
+
1170
+ ['No', 'no', 'foo', 'bar', 'true', 'yes '].each do | invalid_value |
1171
+ it "ignores invalid value #{invalid_value}" do
1172
+ post '/v2/rspec_test_service_stub', '{ "foo": "present", "bar": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8',
1173
+ 'HTTP_X_DEJA_VU' => invalid_value }
1174
+
1175
+ expect(last_response.status).to eq(422)
1176
+ expect(last_response.headers).to_not include('X-Deja-Vu')
1177
+ end
1178
+ end
1179
+ end
1180
+
1181
+ end
1182
+
1183
+ # -------------------------------------------------------------------------
1184
+
1185
+ describe 'service implementation #update' do
1186
+ it 'should complain if the payload is missing' do
1187
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1188
+ patch '/v2/rspec_test_service_stub/1234', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1189
+ expect(last_response.status).to eq(422)
1190
+ result = JSON.parse(last_response.body)
1191
+ expect(result['errors'][0]['code']).to eq('generic.malformed')
1192
+ end
1193
+
1194
+ it 'should complain if the payload is invalid JSON' do
1195
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1196
+ patch '/v2/rspec_test_service_stub/1234', "oiushdfoisuhdf", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1197
+ expect(last_response.status).to eq(422)
1198
+ result = JSON.parse(last_response.body)
1199
+ expect(result['errors'][0]['code']).to eq('generic.malformed')
1200
+ end
1201
+
1202
+ it 'should complain about incorrect to-update data' do
1203
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1204
+ patch '/v2/rspec_test_service_stub/1234', '{ "baz": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1205
+ expect(last_response.status).to eq(422)
1206
+ result = JSON.parse(last_response.body)
1207
+ expect(result['errors'].size).to eq(1)
1208
+ expect(result['errors'][0]['message']).to eq('Field `baz` is an invalid string')
1209
+ end
1210
+
1211
+ it 'should be happy with valid JSON' do
1212
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:update)
1213
+ patch '/v2/rspec_test_service_stub/1234', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1214
+ expect(last_response.status).to eq(200)
1215
+ end
1216
+
1217
+ it 'should be happy with no JSON and no to-update verification' do
1218
+ old = RSpecTestServiceStubInterface.to_update
1219
+ RSpecTestServiceStubInterface.send(:to_update=, nil)
1220
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:update)
1221
+ patch '/v2/rspec_test_service_stub/1234', '{}', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1222
+ expect(last_response.status).to eq(200)
1223
+ RSpecTestServiceStubInterface.send(:to_update=, old)
1224
+ end
1225
+
1226
+ it 'should complain about missing path components' do
1227
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1228
+ patch '/v2/rspec_test_service_stub/', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1229
+ expect(last_response.status).to eq(422)
1230
+ result = JSON.parse(last_response.body)
1231
+ expect(result['errors'][0]['message']).to eq('Expected path components identifying target resource instance for this action')
1232
+ end
1233
+
1234
+ it 'should get called with correct path data' do
1235
+
1236
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:update).once do | ignored_rspec_mock_instance, context |
1237
+ expect(context.request.uri_path_components).to eq(['12345'])
1238
+ expect(context.request.ident).to eq('12345')
1239
+ expect(context.request.uri_path_extension).to eq('tar.gz')
1240
+ end
1241
+
1242
+ patch '/v2/rspec_test_service_stub/12345.tar.gz', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1243
+ expect(last_response.status).to eq(200)
1244
+ end
1245
+
1246
+ it 'should complain if the subclass omits the implementation' do
1247
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:update).and_call_original
1248
+ patch '/v2/rspec_test_service_stub/12345.tar.gz', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1249
+ expect(last_response.status).to eq(500)
1250
+ result = JSON.parse(last_response.body)
1251
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Implementation subclasses must implement 'update'")
1252
+ end
1253
+
1254
+ it 'should complain about prohibited query entries (1)' do
1255
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1256
+ patch '/v2/rspec_test_service_stub/12345?limit=25', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1257
+ expect(last_response.status).to eq(422)
1258
+ result = JSON.parse(last_response.body)
1259
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1260
+ end
1261
+
1262
+ it 'should complain about prohibited query entries (2)' do
1263
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1264
+ patch '/v2/rspec_test_service_stub/12345?imaginary=25', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1265
+ expect(last_response.status).to eq(422)
1266
+ result = JSON.parse(last_response.body)
1267
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1268
+ end
1269
+
1270
+ it 'should respond to embed query parameter' do
1271
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:update).once do | ignored_rspec_mock_instance, context |
1272
+ expect(context.request.embeds).to eq(['embs', 'emb'])
1273
+ end
1274
+
1275
+ patch '/v2/rspec_test_service_stub/12345?_embed=embs,emb', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1276
+ expect(last_response.status).to eq(200)
1277
+ end
1278
+
1279
+ it 'should complain about bad embed query parameter' do
1280
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1281
+ patch '/v2/rspec_test_service_stub/12345?_embed=one,emb,two', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1282
+ expect(last_response.status).to eq(422)
1283
+ result = JSON.parse(last_response.body)
1284
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1285
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1286
+ expect(result['errors'][0]['reference']).to eq('_embed: one\\, two')
1287
+ end
1288
+
1289
+ it 'should respond to reference query parameter' do
1290
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:update).once do | ignored_rspec_mock_instance, context |
1291
+ expect(context.request.references).to eq(['embs', 'emb'])
1292
+ end
1293
+
1294
+ patch '/v2/rspec_test_service_stub/12345?_reference=embs,emb', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1295
+ expect(last_response.status).to eq(200)
1296
+ end
1297
+
1298
+ it 'should complain about bad reference query parameter' do
1299
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:update)
1300
+ patch '/v2/rspec_test_service_stub/12345?_reference=one,emb,two', '{ "baz": "string", "foo": 42 }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1301
+ expect(last_response.status).to eq(422)
1302
+ result = JSON.parse(last_response.body)
1303
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1304
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1305
+ expect(result['errors'][0]['reference']).to eq('_reference: one\\, two')
1306
+ end
1307
+ end
1308
+
1309
+ # -------------------------------------------------------------------------
1310
+
1311
+ describe 'service implementation #delete' do
1312
+ it 'should get called with correct path data' do
1313
+
1314
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:delete).once do | ignored_rspec_mock_instance, context |
1315
+ expect(context.request.uri_path_components).to eq(['12345'])
1316
+ expect(context.request.ident).to eq('12345')
1317
+ expect(context.request.uri_path_extension).to eq('tar.gz')
1318
+ end
1319
+
1320
+ delete '/v2/rspec_test_service_stub/12345.tar.gz', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1321
+ expect(last_response.status).to eq(200)
1322
+ end
1323
+
1324
+ it 'should complain if the subclass omits the implementation' do
1325
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:delete).and_call_original
1326
+ delete '/v2/rspec_test_service_stub/12345.tar.gz', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1327
+ expect(last_response.status).to eq(500)
1328
+ result = JSON.parse(last_response.body)
1329
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Implementation subclasses must implement 'delete'")
1330
+ end
1331
+
1332
+ it 'should complain if any body data is given' do
1333
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:delete)
1334
+ delete '/v2/rspec_test_service_stub/12345.tar.gz', "{}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1335
+ expect(last_response.status).to eq(422)
1336
+ result = JSON.parse(last_response.body)
1337
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1338
+ end
1339
+
1340
+ it 'should complain about prohibited query entries (1)' do
1341
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:delete)
1342
+ delete '/v2/rspec_test_service_stub/12345?limit=25', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1343
+ expect(last_response.status).to eq(422)
1344
+ result = JSON.parse(last_response.body)
1345
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1346
+ end
1347
+
1348
+ it 'should complain about prohibited query entries (2)' do
1349
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:delete)
1350
+ delete '/v2/rspec_test_service_stub/12345?imaginary=25', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1351
+ expect(last_response.status).to eq(422)
1352
+ result = JSON.parse(last_response.body)
1353
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1354
+ end
1355
+
1356
+ it 'should respond to embed query parameter' do
1357
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:delete).once do | ignored_rspec_mock_instance, context |
1358
+ expect(context.request.embeds).to eq(['embs', 'emb'])
1359
+ end
1360
+
1361
+ delete '/v2/rspec_test_service_stub/12345?_embed=embs,emb', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1362
+ expect(last_response.status).to eq(200)
1363
+ end
1364
+
1365
+ it 'should complain about bad embed query parameter' do
1366
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:delete)
1367
+ delete '/v2/rspec_test_service_stub/12345?_embed=one,emb,two', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1368
+ expect(last_response.status).to eq(422)
1369
+ result = JSON.parse(last_response.body)
1370
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1371
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1372
+ expect(result['errors'][0]['reference']).to eq('_embed: one\\, two')
1373
+ end
1374
+
1375
+ it 'should respond to reference query parameter' do
1376
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:delete).once do | ignored_rspec_mock_instance, context |
1377
+ expect(context.request.references).to eq(['embs', 'emb'])
1378
+ end
1379
+
1380
+ delete '/v2/rspec_test_service_stub/12345?_reference=embs,emb', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1381
+ expect(last_response.status).to eq(200)
1382
+ end
1383
+
1384
+ it 'should complain about bad reference query parameter' do
1385
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:delete)
1386
+ delete '/v2/rspec_test_service_stub/12345?_reference=one,emb,two', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1387
+ expect(last_response.status).to eq(422)
1388
+ result = JSON.parse(last_response.body)
1389
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1390
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
1391
+ expect(result['errors'][0]['reference']).to eq('_reference: one\\, two')
1392
+ end
1393
+ end
1394
+ end
1395
+ end
1396
+
1397
+
1398
+ ###############################################################################
1399
+ # Multiple incorrectly configured endpoints
1400
+ ###############################################################################
1401
+
1402
+
1403
+ class RSpecTestBrokenServiceStub < Hoodoo::Services::Service
1404
+ comprised_of RSpecTestServiceStubInterface,
1405
+ RSpecTestMatchingServiceStubInterface # I.e. same endpoint twice
1406
+ end
1407
+
1408
+ describe Hoodoo::Services::Middleware do
1409
+ context 'bad endpoint configuration' do
1410
+
1411
+ def app
1412
+ Rack::Builder.new do
1413
+ use Hoodoo::Services::Middleware
1414
+ run RSpecTestBrokenServiceStub.new
1415
+ end
1416
+ end
1417
+
1418
+ it 'should cause a routing exception' do
1419
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to_not receive(:list)
1420
+ get '/v2/rspec_test_service_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1421
+ expect(last_response.status).to eq(500)
1422
+ result = JSON.parse(last_response.body)
1423
+ expect(result['errors'][0]['code']).to eq('platform.fault')
1424
+ expect(result['errors'][0]['message']).to eq('Multiple service endpoint matches - internal server configuration fault')
1425
+ end
1426
+ end
1427
+ end
1428
+
1429
+
1430
+ ###############################################################################
1431
+ # Multiple correctly configured endpoints
1432
+ ###############################################################################
1433
+
1434
+
1435
+ class RSpecTestServiceV1StubImplementation < Hoodoo::Services::Implementation
1436
+ end
1437
+
1438
+ class RSpecTestServiceV1StubInterface < Hoodoo::Services::Interface
1439
+ interface :RSpecTestResource do
1440
+ endpoint :rspec_test_service_stub, RSpecTestServiceV1StubImplementation
1441
+ actions :list, :create, :update
1442
+ end
1443
+ end
1444
+
1445
+ class RSpecTestServiceAltStubImplementation < Hoodoo::Services::Implementation
1446
+ end
1447
+
1448
+ class RSpecTestServiceAltStubInterface < Hoodoo::Services::Interface
1449
+ interface :RSpecTestResource do
1450
+ version 2
1451
+ endpoint :rspec_test_service_alt_stub, RSpecTestServiceAltStubImplementation
1452
+ end
1453
+ end
1454
+
1455
+ class RSpecTestMultipleEndpointServiceStub < Hoodoo::Services::Service
1456
+ comprised_of RSpecTestServiceStubInterface,
1457
+ RSpecTestServiceV1StubInterface,
1458
+ RSpecTestServiceAltStubInterface
1459
+ end
1460
+
1461
+ describe Hoodoo::Services::Middleware do
1462
+
1463
+ def app
1464
+ Rack::Builder.new do
1465
+ use Hoodoo::Services::Middleware
1466
+ run RSpecTestMultipleEndpointServiceStub.new
1467
+ end
1468
+ end
1469
+
1470
+ context 'multiple endpoints and versions' do
1471
+ it 'should return 404 with no matching route' do
1472
+ get '/v1/nowhere/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1473
+ expect(last_response.status).to eq(404)
1474
+ end
1475
+
1476
+ it 'should route to the V1 endpoint' do
1477
+ expect_any_instance_of(RSpecTestServiceV1StubImplementation).to receive(:list)
1478
+ get '/v1/rspec_test_service_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1479
+ expect(last_response.status).to eq(200)
1480
+ end
1481
+
1482
+ it 'should route to the V2 endpoint' do
1483
+ expect_any_instance_of(RSpecTestServiceStubImplementation).to receive(:list)
1484
+ get '/v2/rspec_test_service_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1485
+ expect(last_response.status).to eq(200)
1486
+ end
1487
+
1488
+ it 'should route to the V2 alternative endpoint' do
1489
+ expect_any_instance_of(RSpecTestServiceAltStubImplementation).to receive(:list)
1490
+ get '/v2/rspec_test_service_alt_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1491
+ expect(last_response.status).to eq(200)
1492
+ end
1493
+ end
1494
+
1495
+ context 'with limited supported actions' do
1496
+ it 'should accept supported actions' do
1497
+ expect_any_instance_of(RSpecTestServiceV1StubImplementation).to receive(:list)
1498
+ expect_any_instance_of(RSpecTestServiceV1StubImplementation).to receive(:create)
1499
+ expect_any_instance_of(RSpecTestServiceV1StubImplementation).to receive(:update)
1500
+
1501
+ get '/v1/rspec_test_service_stub/', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1502
+ expect(last_response.status).to eq(200)
1503
+
1504
+ post '/v1/rspec_test_service_stub/', "{}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1505
+ expect(last_response.status).to eq(200)
1506
+
1507
+ patch '/v1/rspec_test_service_stub/1234', "{}", { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1508
+ expect(last_response.status).to eq(200)
1509
+ end
1510
+
1511
+ it 'should reject unsupported actions' do
1512
+ expect_any_instance_of(RSpecTestServiceV1StubImplementation).to_not receive(:show)
1513
+ expect_any_instance_of(RSpecTestServiceV1StubImplementation).to_not receive(:delete)
1514
+
1515
+ get '/v1/rspec_test_service_stub/1234', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1516
+ expect(last_response.status).to eq(405)
1517
+ result = JSON.parse(last_response.body)
1518
+ expect(result['errors'][0]['code']).to eq('platform.method_not_allowed')
1519
+ expect(result['errors'][0]['message']).to eq("Service endpoint '/v1/rspec_test_service_stub' does not support HTTP method 'GET' yielding action 'show'")
1520
+
1521
+ delete '/v1/rspec_test_service_stub/1234', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1522
+ expect(last_response.status).to eq(405)
1523
+ result = JSON.parse(last_response.body)
1524
+ expect(result['errors'][0]['code']).to eq('platform.method_not_allowed')
1525
+ expect(result['errors'][0]['message']).to eq("Service endpoint '/v1/rspec_test_service_stub' does not support HTTP method 'DELETE' yielding action 'delete'")
1526
+ end
1527
+ end
1528
+ end
1529
+
1530
+
1531
+ ###############################################################################
1532
+ # Custom service error descriptions
1533
+ ###############################################################################
1534
+
1535
+
1536
+ class RSpecTestServiceWithErrorsStubImplementation < Hoodoo::Services::Implementation
1537
+ end
1538
+
1539
+ class RSpecTestServiceWithErrorsStubInterface < Hoodoo::Services::Interface
1540
+ interface :RSpecTestResource do
1541
+ version 42
1542
+ endpoint :rspec_test_service_with_errors_stub, RSpecTestServiceWithErrorsStubImplementation
1543
+ errors_for :rspec do
1544
+ error 'hello', :status => 418, 'message' => "I'm a teapot", 'reference' => { :rfc => '2324' }
1545
+ end
1546
+ end
1547
+ end
1548
+
1549
+ class RSpecTestServiceWithErrorsStub < Hoodoo::Services::Service
1550
+ comprised_of RSpecTestServiceWithErrorsStubInterface
1551
+ end
1552
+
1553
+ describe Hoodoo::Services::Middleware do
1554
+ def app
1555
+ Rack::Builder.new do
1556
+ use Hoodoo::Services::Middleware
1557
+ run RSpecTestServiceWithErrorsStub.new
1558
+ end
1559
+ end
1560
+
1561
+ it 'should define custom errors' do
1562
+ expect_any_instance_of(RSpecTestServiceWithErrorsStubImplementation).to receive(:list).once do | ignored_rspec_mock_instance, context |
1563
+ expect(context.response.errors.instance_variable_get('@descriptions').describe('rspec.hello')).to eq({ 'status' => 418, 'message' => "I'm a teapot", 'reference' => { 'rfc' => '2324' } })
1564
+ end
1565
+
1566
+ get '/v42/rspec_test_service_with_errors_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1567
+ expect(last_response.status).to eq(200)
1568
+ end
1569
+ end