hoodoo 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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