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,1440 @@
1
+ ###############################################################################
2
+ # Remote inter-resource calls
3
+ #
4
+ # Start multiple HTTP server instances in threads and have them talk to each
5
+ # other in order to test remote inter-resource calls.
6
+ ###############################################################################
7
+
8
+ require 'spec_helper'
9
+ require 'json'
10
+
11
+ # First, a test service comprised of a couple of 'echo' variants which we use
12
+ # to make sure they're both correctly stored in the DRb registry.
13
+
14
+ class TestEchoImplementation < Hoodoo::Services::Implementation
15
+
16
+ public
17
+
18
+ def list( context )
19
+
20
+ # Deliberate error generation hook.
21
+ #
22
+ if context.request.list.offset == 42
23
+ context.response.add_error( 'platform.malformed' )
24
+ return
25
+ end
26
+
27
+ context.response.set_resources(
28
+ [
29
+ { 'list0' => TestEchoImplementation.to_h( context ) },
30
+ { 'list1' => TestEchoImplementation.to_h( context ) },
31
+ { 'list2' => TestEchoImplementation.to_h( context ) }
32
+ ],
33
+ 49
34
+ )
35
+ end
36
+
37
+ def show( context )
38
+ if context.request.uri_path_components[ 0 ] == 'return_error'
39
+ context.response.add_error(
40
+ 'generic.invalid_string',
41
+ :message => 'Returning error as requested',
42
+ :reference => { :another => 'no other ident', :field_name => 'no ident' }
43
+ )
44
+ elsif context.request.uri_path_components[ 0 ] == 'return_invalid_json'
45
+ context.response.body = 'Hello, world'
46
+ else
47
+ context.response.body = { 'show' => TestEchoImplementation.to_h( context ) }
48
+ end
49
+ end
50
+
51
+ def create( context )
52
+
53
+ # Deliberate error generation hook.
54
+ #
55
+ if context.request.body.has_key?( 'return_error' )
56
+ context.response.add_error( 'platform.malformed' )
57
+ return
58
+ end
59
+
60
+ # If asked to add another error to the response, we're testing
61
+ # the deja-vu stuff including making sure that errors are still returned
62
+ # if any non-"invalid duplication" stuff exists. As long as the error
63
+ # code requires no reference data or only requires a "field_name", this
64
+ # will work OK.
65
+ #
66
+ if context.request.deja_vu
67
+ additional_error_code = context.request.body[ 'additional_error' ] || 'generic.invalid_duplication'
68
+ reference = { :reference => { :field_name => 'deja_vu' } }
69
+
70
+ context.response.add_error( 'generic.invalid_duplication', reference )
71
+ context.response.add_error( additional_error_code, reference )
72
+
73
+ return
74
+ end
75
+
76
+ context.response.set_resource( { 'create' => TestEchoImplementation.to_h( context ) } )
77
+ end
78
+
79
+ def update( context )
80
+
81
+ # Deliberate error generation hook.
82
+ #
83
+ if context.request.ident == 'return_error'
84
+ context.response.add_error( 'platform.malformed' )
85
+ return
86
+ end
87
+
88
+ context.response.add_header( 'X-Example-Header', 'example' )
89
+ context.response.body = { 'update' => TestEchoImplementation.to_h( context ) }
90
+ end
91
+
92
+ def delete( context )
93
+ return context.response.not_found( context.request.ident ) if context.request.ident == 'simulate_404'
94
+ context.response.body = { 'delete' => TestEchoImplementation.to_h( context ) }
95
+ end
96
+
97
+ # Used by both this class and later code in this file.
98
+ #
99
+ def self.to_h( context )
100
+ {
101
+ 'locale' => context.request.locale,
102
+ 'dated_at' => context.request.dated_at.to_s,
103
+ 'dated_from' => context.request.dated_from.to_s,
104
+ 'deja_vu' => context.request.deja_vu.to_s,
105
+ 'resource_uuid' => context.request.resource_uuid.to_s,
106
+ 'body' => context.request.body,
107
+ 'uri_path_components' => context.request.uri_path_components,
108
+ 'uri_path_extension' => context.request.uri_path_extension,
109
+ 'list_offset' => context.request.list.offset,
110
+ 'list_limit' => context.request.list.limit,
111
+ 'list_sort_data' => context.request.list.sort_data,
112
+ 'list_search_data' => context.request.list.search_data,
113
+ 'list_filter_data' => context.request.list.filter_data,
114
+ 'embeds' => context.request.embeds,
115
+ 'references' => context.request.references
116
+ }
117
+ end
118
+ end
119
+
120
+ class TestEchoInterface < Hoodoo::Services::Interface
121
+ interface :TestEcho do
122
+ version 2
123
+ endpoint :test_some_echoes, TestEchoImplementation
124
+
125
+ embeds :embed_one, :embed_two
126
+
127
+ to_list do
128
+ search :search_one, :search_two
129
+ filter :filter_one, :filter_two
130
+ end
131
+ end
132
+ end
133
+
134
+ class TestEchoQuietImplementation < Hoodoo::Services::Implementation
135
+
136
+ public
137
+
138
+ def show( context )
139
+ context.response.body = { 'show' => TestEchoImplementation.to_h( context ) }
140
+ end
141
+
142
+ def list( context )
143
+ expectable_hook( context )
144
+ context.response.set_resources( [ context.request.headers ], 1 )
145
+ end
146
+
147
+ private
148
+
149
+ def expectable_hook( context )
150
+ end
151
+ end
152
+
153
+ class TestEchoQuietInterface < Hoodoo::Services::Interface
154
+ interface :TestEchoQuiet do
155
+ endpoint :test_echo_quiet, TestEchoQuietImplementation
156
+ actions :show, :list
157
+ end
158
+ end
159
+
160
+ class TestEchoService < Hoodoo::Services::Service
161
+ comprised_of TestEchoInterface, TestEchoQuietInterface
162
+ end
163
+
164
+ # Now the calling service that'll call over to the echo services above.
165
+
166
+ class TestCallImplementation < Hoodoo::Services::Implementation
167
+ def list( context )
168
+ resource = context.resource( :TestEcho, 2 )
169
+ result = resource.list(
170
+ {
171
+ 'offset' => context.request.list.offset,
172
+ 'limit' => context.request.list.limit,
173
+ 'sort' => context.request.list.sort_data.keys.first,
174
+ 'direction' => context.request.list.sort_data.values.first,
175
+ 'search' => context.request.list.search_data,
176
+ 'filter' => context.request.list.filter_data,
177
+ '_embed' => context.request.embeds,
178
+ '_reference' => context.request.references
179
+ }
180
+ )
181
+
182
+ expectable_result_hook( result )
183
+
184
+ return if result.adds_errors_to?( context.response.errors )
185
+
186
+ context.response.set_resources(
187
+ [
188
+ { 'listA' => result },
189
+ { 'listB' => result },
190
+ { 'listC' => result },
191
+ { 'options' => result.response_options }
192
+ ],
193
+ ( result.dataset_size || 0 ) + 2
194
+ )
195
+ end
196
+
197
+ def show( context )
198
+
199
+ # (Exercise 'uri_path_components' array vs 'ident' in passing).
200
+
201
+ repeat = context.request.ident == 'ensure_repeated_use_works' ? 10 : 1
202
+
203
+ 1.upto( repeat ) do
204
+
205
+ resource = if ( context.request.uri_path_components[ 0 ] == 'generate_404' )
206
+ context.resource( :NotFound, 42 )
207
+ else
208
+ context.resource( :TestEcho, 2 )
209
+ end
210
+
211
+ result = resource.show(
212
+ context.request.uri_path_components.join( ',' ),
213
+ {
214
+ '_embed' => context.request.embeds,
215
+ '_reference' => context.request.references
216
+ }
217
+ )
218
+
219
+ expectable_result_hook( result )
220
+
221
+ context.response.add_errors( result.platform_errors )
222
+ context.response.body = { 'show' => result, 'options' => result.response_options }
223
+
224
+ end
225
+ end
226
+
227
+ def create( context )
228
+ endpoint = context.resource( :TestEcho, 2, dated_from: context.request.dated_from )
229
+
230
+ if context.request.body[ 'foo' ] == 'specify_uuid'
231
+
232
+ # If given the magic string "specify_uuid" in the mandatory resource
233
+ # text field "foo", then specifically make an inter-resource call with
234
+ # a resource UUID specified for the inner resource. This checks the
235
+ # permissions handling on "internal" inter-resource calls.
236
+ #
237
+ # This is for *inter-resource* calls *from* this resource to the target
238
+ # resource and has nothing to do with anything the top-level API caller
239
+ # specified.
240
+ #
241
+ endpoint.resource_uuid = Hoodoo::UUID.generate()
242
+
243
+ elsif context.request.body.has_key?( 'deja_vu_in_other_resource' )
244
+
245
+ # This tests an inter-resource call specifying deja-vu and dealing with
246
+ # responses.
247
+ #
248
+ endpoint.deja_vu = true
249
+
250
+ end
251
+
252
+ result = endpoint.create(
253
+ context.request.body,
254
+ {
255
+ '_embed' => context.request.embeds,
256
+ '_reference' => context.request.references
257
+ }
258
+ )
259
+
260
+ expectable_result_hook( result )
261
+
262
+ context.response.add_errors( result.platform_errors )
263
+ context.response.body = { 'create' => result, 'options' => result.response_options }
264
+ end
265
+
266
+ def update( context )
267
+ resource = context.resource( :TestEcho, 2 )
268
+ result = resource.update(
269
+ context.request.uri_path_components.join( ',' ),
270
+ context.request.body,
271
+ {
272
+ '_embed' => context.request.embeds,
273
+ '_reference' => context.request.references
274
+ }
275
+ )
276
+
277
+ expectable_result_hook( result )
278
+
279
+ context.response.add_errors( result.platform_errors )
280
+ context.response.body = { 'update' => result, 'options' => result.response_options }
281
+ end
282
+
283
+ def delete( context )
284
+ resource = context.resource( :TestEcho, 2 )
285
+ result = resource.delete(
286
+ context.request.uri_path_components.join( ',' ),
287
+ {
288
+ '_embed' => context.request.embeds,
289
+ '_reference' => context.request.references
290
+ }
291
+ )
292
+
293
+ expectable_result_hook( result )
294
+
295
+ context.response.add_errors( result.platform_errors )
296
+ context.response.body = { 'delete' => result, 'options' => result.response_options }
297
+ end
298
+
299
+ # ...So we can expect any instance of this class to receive this message
300
+ # and check on the data it was given.
301
+ #
302
+ def expectable_result_hook( result )
303
+ end
304
+ end
305
+
306
+ class TestCallInterface < Hoodoo::Services::Interface
307
+ interface :TestCall do
308
+ endpoint :test_call, TestCallImplementation
309
+ end
310
+ end
311
+
312
+ class TestCallService < Hoodoo::Services::Service
313
+ comprised_of TestCallInterface
314
+ end
315
+
316
+ # A helper method for a set of common expectations.
317
+
318
+ def expect_response_headers_on( response, should_not_have_called_service = false )
319
+ expect( response[ 'X-Interaction-ID' ] ).to_not be_nil
320
+ expect( Hoodoo::UUID.valid?( response[ 'X-Interaction-ID' ] ) ).to eq( true )
321
+
322
+ if should_not_have_called_service
323
+ expect( response[ 'X-Service-Response-Time' ] ).to be_nil
324
+ else
325
+ expect( response[ 'X-Service-Response-Time' ] ).to_not be_nil
326
+ end
327
+ end
328
+
329
+ # And Finally - the tests.
330
+
331
+ describe 'DRb start timeout' do
332
+ context 'for test coverage purposes' do
333
+ it 'checks for timeouts' do
334
+ expect( DRbObject ).to receive( :new_with_uri ).once.and_raise( DRb::DRbConnError )
335
+ expect( DRbObject ).to receive( :new_with_uri ).at_least( :once ).and_raise( Timeout::Error )
336
+
337
+ spec_helper_http(
338
+ port: spec_helper_start_svc_app_in_thread_for( TestEchoService ),
339
+ path: '/v2/test_some_echoes'
340
+ )
341
+ end
342
+ end
343
+ end
344
+
345
+ describe Hoodoo::Services::Middleware do
346
+
347
+ before :each do
348
+ @test_uuid = Hoodoo::UUID.generate()
349
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
350
+ @test_session = @old_test_session.dup
351
+ permissions = Hoodoo::Services::Permissions.new # (this is "default-else-deny")
352
+ permissions.set_default_fallback( Hoodoo::Services::Permissions::ALLOW )
353
+ @test_session.permissions = permissions
354
+ @test_session.scoping = @test_session.scoping.dup
355
+ @test_session.scoping.authorised_http_headers = [] # (no secured headers allowed to start with)
356
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
357
+ end
358
+
359
+ after :each do
360
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
361
+ end
362
+
363
+ before :all do
364
+ @port = spec_helper_start_svc_app_in_thread_for( TestEchoService )
365
+ end
366
+
367
+ # Although tests can run in random order so we can't force this set to come
368
+ # first, at least having this set present validates all of the HTTP behaviour
369
+ # we expect to work in the echo service. If these tests fail, all bets are
370
+ # off for anything else tested here.
371
+ #
372
+ context 'with in-thread HTTP service' do
373
+
374
+ before :example, :check_callbacks => true do
375
+ expect_any_instance_of( TestEchoImplementation ).to receive( :before ).once
376
+ expect_any_instance_of( TestEchoImplementation ).to receive( :after ).once
377
+ end
378
+
379
+ before :example, :check_quiet_callbacks => true do
380
+ expect_any_instance_of( TestEchoQuietImplementation ).to receive( :before ).once
381
+ expect_any_instance_of( TestEchoQuietImplementation ).to receive( :after ).once
382
+ end
383
+
384
+ def list_things( locale: nil, dated_at: nil, deja_vu: nil )
385
+ headers = {}
386
+ headers[ 'Accept-Language' ] = locale unless locale.nil?
387
+ headers[ 'X-Dated-At' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_at ) unless dated_at.nil?
388
+ headers[ 'X-Deja-Vu' ] = 'yes' if deja_vu == true
389
+
390
+ response = spec_helper_http(
391
+ port: @port,
392
+ path: '/v2/test_some_echoes.tar.gz?limit=25&offset=75&_reference=embed_one,embed_two',
393
+ headers: headers
394
+ )
395
+
396
+ expect( response.code ).to eq( '200' )
397
+ expect_response_headers_on( response )
398
+
399
+ parsed = JSON.parse( response.body )
400
+
401
+ expect( parsed[ '_data' ]).to_not be_nil
402
+ expect( parsed[ '_data' ][ 0 ]).to_not be_nil
403
+ expect( parsed[ '_data' ][ 0 ][ 'list0' ] ).to eq(
404
+ {
405
+ 'locale' => locale.nil? ? 'en-nz' : locale,
406
+ 'dated_at' => dated_at.to_s,
407
+ 'dated_from' => '',
408
+ 'deja_vu' => deja_vu.to_s,
409
+ 'resource_uuid' => '',
410
+ 'body' => nil,
411
+ 'uri_path_components' => [],
412
+ 'uri_path_extension' => 'tar.gz',
413
+ 'list_offset' => 75,
414
+ 'list_limit' => 25,
415
+ 'list_sort_data' => { 'created_at' => 'desc' },
416
+ 'list_search_data' => {},
417
+ 'list_filter_data' => {},
418
+ 'embeds' => [],
419
+ 'references' => [ 'embed_one', 'embed_two' ]
420
+ }
421
+ )
422
+ expect( parsed[ '_dataset_size' ] ).to eq( 49 )
423
+ end
424
+
425
+ it 'lists things with callbacks', :check_callbacks => true do
426
+ list_things()
427
+ end
428
+
429
+ it 'list things without callbacks' do
430
+ list_things()
431
+ end
432
+
433
+ it 'lists things with a custom locale and dated-at time' do
434
+ list_things( locale: 'foo', dated_at: DateTime.now )
435
+ end
436
+
437
+ it 'should be able to list quiet things too, reporting HTTP headers', :check_quiet_callbacks => true do
438
+
439
+ # Did this test give you a 500? Chances are it's the "raise" below,
440
+ # but checking 'test.log' or adding "puts response.body.inspect" a
441
+ # bit further down will let you know for sure.
442
+
443
+ expect_any_instance_of( TestEchoQuietImplementation ).to receive( :expectable_hook ) { | ignored, context |
444
+ raise 'Test failed as context.request.headers not frozen' unless context.request.headers.frozen?
445
+ }
446
+
447
+ response = spec_helper_http(
448
+ port: @port,
449
+ path: '/v1/test_echo_quiet'
450
+ )
451
+
452
+ expect( response.code ).to eq( '200' )
453
+ expect_response_headers_on( response )
454
+
455
+ parsed = JSON.parse( response.body )
456
+
457
+ expect( parsed[ '_data' ] ).to eq( [ {
458
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
459
+ 'HTTP_CONNECTION' => 'close',
460
+ 'HTTP_VERSION' => 'HTTP/1.1',
461
+ 'HTTP_HOST' => "127.0.0.1:#{ @port }"
462
+ } ] )
463
+ end
464
+
465
+ # Code coverage: At present the middleware always calls its "remove
466
+ # expected errors" method if deja-vu is on and there are errors in the
467
+ # response. This method then filters based on action, with an early
468
+ # exit for non-create, non-delete cases. We're testing that here.
469
+ #
470
+ it 'lists things without worrying about deja-vu' do
471
+ expect_any_instance_of( TestEchoImplementation ).to receive( :list ).once do | ignored_rspec_mock_instance, context |
472
+ context.response.not_found( 'some_error' )
473
+ end
474
+
475
+ expect_any_instance_of( Hoodoo::Services::Middleware ).to receive( :remove_expected_errors_when_experiencing_deja_vu ).once.and_call_original
476
+
477
+ response = spec_helper_http(
478
+ port: @port,
479
+ path: '/v2/test_some_echoes',
480
+ headers: { 'X-Deja-Vu' => 'yes' }
481
+ )
482
+
483
+ expect( response.code ).to eq( '404' )
484
+ expect_response_headers_on( response )
485
+ end
486
+
487
+ def show_things( locale: nil, dated_at: nil )
488
+ headers = {}
489
+ headers[ 'Accept-Language' ] = locale unless locale.nil?
490
+ headers[ 'X-Dated-At' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_at ) unless dated_at.nil?
491
+
492
+ response = spec_helper_http(
493
+ port: @port,
494
+ path: '/v2/test_some_echoes/one/two.tar.gz?_reference=embed_one,embed_two',
495
+ headers: headers
496
+ )
497
+
498
+ expect( response.code ).to eq( '200' )
499
+ expect_response_headers_on( response )
500
+
501
+ parsed = JSON.parse( response.body )
502
+
503
+ expect( parsed[ 'show' ] ).to_not be_nil
504
+ expect( parsed[ 'show' ] ).to eq(
505
+ {
506
+ 'locale' => locale.nil? ? 'en-nz' : locale,
507
+ 'dated_at' => dated_at.to_s,
508
+ 'dated_from' => '',
509
+ 'deja_vu' => '',
510
+ 'resource_uuid' => '',
511
+ 'body' => nil,
512
+ 'uri_path_components' => [ 'one', 'two' ],
513
+ 'uri_path_extension' => 'tar.gz',
514
+ 'list_offset' => 0,
515
+ 'list_limit' => 50,
516
+ 'list_sort_data' => { 'created_at' => 'desc' },
517
+ 'list_search_data' => {},
518
+ 'list_filter_data' => {},
519
+ 'embeds' => [],
520
+ 'references' => [ 'embed_one', 'embed_two' ]
521
+ }
522
+ )
523
+ end
524
+
525
+ it 'shows_things_with_callbacks', :check_callbacks => true do
526
+ show_things( locale: 'fr' )
527
+ end
528
+
529
+ it 'shows_things_without_callbacks' do
530
+ show_things()
531
+ end
532
+
533
+ it 'shows things with a custom locale and dated-at time' do
534
+ show_things( locale: 'bar', dated_at: DateTime.now )
535
+ end
536
+
537
+ it 'should be able to show quiet things too', :check_quiet_callbacks => true do
538
+
539
+ response = spec_helper_http(
540
+ port: @port,
541
+ path: '/v1/test_echo_quiet/some_uuid'
542
+ )
543
+
544
+ expect( response.code ).to eq( '200' )
545
+ expect_response_headers_on( response )
546
+
547
+ parsed = JSON.parse( response.body )
548
+
549
+ expect( parsed[ 'show' ] ).to_not be_nil
550
+ expect( parsed[ 'show' ] ).to eq(
551
+ {
552
+ 'locale' => 'en-nz',
553
+ 'dated_at' => '',
554
+ 'dated_from' => '',
555
+ 'deja_vu' => '',
556
+ 'resource_uuid' => '',
557
+ 'body' => nil,
558
+ 'uri_path_components' => [ 'some_uuid' ],
559
+ 'uri_path_extension' => '',
560
+ 'list_offset' => 0,
561
+ 'list_limit' => 50,
562
+ 'list_sort_data' => { 'created_at' => 'desc' },
563
+ 'list_search_data' => {},
564
+ 'list_filter_data' => {},
565
+ 'embeds' => [],
566
+ 'references' => []
567
+ }
568
+ )
569
+ end
570
+
571
+ def create_things( locale: nil, dated_from: nil, deja_vu: nil, resource_uuid: nil )
572
+ headers = {}
573
+ headers[ 'Accept-Language' ] = locale unless locale.nil?
574
+ headers[ 'X-Resource-UUID' ] = resource_uuid unless resource_uuid.nil?
575
+ headers[ 'X-Dated-From' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_from ) unless dated_from.nil?
576
+ headers[ 'X-Deja-Vu' ] = 'yes' if deja_vu == true
577
+
578
+ response = spec_helper_http(
579
+ klass: Net::HTTP::Post,
580
+ port: @port,
581
+ path: '/v2/test_some_echoes.json?_embed=embed_one,embed_two',
582
+ body: { 'foo' => 'bar', 'baz' => 'boo' }.to_json,
583
+ headers: headers
584
+ )
585
+
586
+ if deja_vu
587
+ expect( response.code ).to eq( '204' )
588
+ expect( response.body ).to be_nil
589
+ expect_response_headers_on( response )
590
+
591
+ else
592
+ expect( response.code ).to eq( '200' )
593
+ expect_response_headers_on( response )
594
+
595
+ parsed = JSON.parse( response.body )
596
+
597
+ expected_body = { 'foo' => 'bar', 'baz' => 'boo' }
598
+ expected_body[ 'id' ] = resource_uuid unless resource_uuid.nil?
599
+
600
+ expect( parsed[ 'create' ] ).to_not be_nil
601
+ expect( parsed[ 'create' ] ).to eq(
602
+ {
603
+ 'locale' => locale.nil? ? 'en-nz' : locale,
604
+ 'dated_at' => '',
605
+ 'dated_from' => dated_from.to_s,
606
+ 'deja_vu' => deja_vu.to_s,
607
+ 'resource_uuid' => resource_uuid.to_s,
608
+ 'body' => expected_body,
609
+ 'uri_path_components' => [],
610
+ 'uri_path_extension' => 'json',
611
+ 'list_offset' => 0,
612
+ 'list_limit' => 50,
613
+ 'list_sort_data' => { 'created_at' => 'desc' },
614
+ 'list_search_data' => {},
615
+ 'list_filter_data' => {},
616
+ 'embeds' => [ 'embed_one', 'embed_two' ],
617
+ 'references' => []
618
+ }
619
+ )
620
+
621
+ end
622
+ end
623
+
624
+ it 'creates things with callbacks', :check_callbacks => true do
625
+ create_things()
626
+ end
627
+
628
+ it 'creates things without callbacks' do
629
+ create_things()
630
+ end
631
+
632
+ it 'creates things with a custom locale, dated_from and deja_vu' do
633
+ create_things( locale: 'baz', dated_from: DateTime.now, deja_vu: true )
634
+ end
635
+
636
+ # Extra test coverage for this is present in middleware_create_update_spec.rb.
637
+ #
638
+ it 'creates things with a custom UUID given permission' do
639
+ @test_session.scoping.authorised_http_headers = [ 'HTTP_X_RESOURCE_UUID' ]
640
+ create_things(resource_uuid: Hoodoo::UUID.generate())
641
+ end
642
+
643
+ # Extra test coverage for this is present in middleware_create_update_spec.rb.
644
+ #
645
+ it 'fails to create things with a custom UUID if not given permission' do
646
+ response = spec_helper_http(
647
+ klass: Net::HTTP::Post,
648
+ port: @port,
649
+ path: '/v2/test_some_echoes.json',
650
+ body: { 'foo' => 'bar', 'baz' => 'boo' }.to_json,
651
+ headers: { 'X-Resource-UUID' => Hoodoo::UUID.generate() }
652
+ )
653
+
654
+ expect( response.code ).to eq( '403' )
655
+ expect_response_headers_on( response, true ) # true => shouldn't have called service
656
+
657
+ parsed = JSON.parse( response.body )
658
+
659
+ expect( parsed[ 'errors' ].size ).to eq( 1 )
660
+ expect( parsed[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
661
+ expect( parsed[ 'errors' ][ 0 ][ 'reference' ] ).to be_nil # Ensure no information disclosure vulnerability
662
+ end
663
+
664
+ it 'gets a 204 with deja-vu and duplication' do
665
+ response = spec_helper_http(
666
+ klass: Net::HTTP::Post,
667
+ port: @port,
668
+ path: '/v2/test_some_echoes',
669
+ body: { 'foo' => 'bar' }.to_json,
670
+ headers: { 'X-Deja-Vu' => 'yes' }
671
+ )
672
+
673
+ expect( response.code ).to eq( '204' )
674
+ expect( response.body ).to be_nil
675
+
676
+ expect_response_headers_on( response )
677
+ expect( response[ 'X-Deja-Vu' ] ).to eq( 'confirmed' )
678
+ end
679
+
680
+ it 'gets non-204 with deja-vu and duplication plus other errors' do
681
+ response = spec_helper_http(
682
+ klass: Net::HTTP::Post,
683
+ port: @port,
684
+ path: '/v2/test_some_echoes',
685
+ body: { 'foo' => 'bar', 'additional_error' => 'generic.invalid_decimal' }.to_json,
686
+ headers: { 'X-Deja-Vu' => 'yes' }
687
+ )
688
+
689
+ expect( response.code ).to eq( '422' )
690
+ expect_response_headers_on( response )
691
+
692
+ parsed = JSON.parse( response.body )
693
+
694
+ expect( parsed[ 'errors' ].size ).to eq( 2 )
695
+ expect( parsed[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'generic.invalid_duplication' )
696
+ expect( parsed[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'deja_vu' )
697
+ expect( parsed[ 'errors' ][ 1 ][ 'code' ] ).to eq( 'generic.invalid_decimal' )
698
+ expect( parsed[ 'errors' ][ 1 ][ 'reference' ] ).to eq( 'deja_vu' )
699
+ end
700
+
701
+ def update_things( locale: nil )
702
+ headers = {}
703
+ headers[ 'Accept-Language' ] = locale unless locale.nil?
704
+
705
+ response = spec_helper_http(
706
+ klass: Net::HTTP::Patch,
707
+ port: @port,
708
+ path: '/v2/test_some_echoes/a/b.json?_embed=embed_one',
709
+ body: { 'foo' => 'boo', 'baz' => 'bar' }.to_json,
710
+ headers: headers
711
+ )
712
+
713
+ expect( response.code ).to eq( '200' )
714
+ expect_response_headers_on( response )
715
+
716
+ parsed = JSON.parse( response.body )
717
+
718
+ expect( parsed[ 'update' ] ).to_not be_nil
719
+ expect( parsed[ 'update' ] ).to eq(
720
+ {
721
+ 'locale' => locale.nil? ? 'en-nz' : locale,
722
+ 'dated_at' => '',
723
+ 'dated_from' => '',
724
+ 'deja_vu' => '',
725
+ 'resource_uuid' => '',
726
+ 'body' => { 'foo' => 'boo', 'baz' => 'bar' },
727
+ 'uri_path_components' => [ 'a', 'b' ],
728
+ 'uri_path_extension' => 'json',
729
+ 'list_offset' => 0,
730
+ 'list_limit' => 50,
731
+ 'list_sort_data' => { 'created_at' => 'desc' },
732
+ 'list_search_data' => {},
733
+ 'list_filter_data' => {},
734
+ 'embeds' => [ 'embed_one' ],
735
+ 'references' => []
736
+ }
737
+ )
738
+ end
739
+
740
+ it 'updates things with callbacks', :check_callbacks => true do
741
+ update_things()
742
+ end
743
+
744
+ it 'updates things without callbacks' do
745
+ update_things()
746
+ end
747
+
748
+ it 'updates things with a custom locale' do
749
+ update_things( locale: 'boo' )
750
+ end
751
+
752
+ def delete_things( locale: nil, deja_vu: nil )
753
+ headers = {}
754
+ headers[ 'Accept-Language' ] = locale unless locale.nil?
755
+ headers[ 'X-Deja-Vu' ] = 'yes' if deja_vu == true
756
+
757
+ response = spec_helper_http(
758
+ klass: Net::HTTP::Delete,
759
+ port: @port,
760
+ path: '/v2/test_some_echoes/aa/bb.xml.gz?_embed=embed_two',
761
+ headers: headers
762
+ )
763
+
764
+ expect( response.code ).to eq( '200' )
765
+ expect_response_headers_on( response )
766
+
767
+ parsed = JSON.parse( response.body )
768
+
769
+ expect( parsed[ 'delete' ] ).to_not be_nil
770
+ expect( parsed[ 'delete' ] ).to eq(
771
+ {
772
+ 'locale' => locale.nil? ? 'en-nz' : locale,
773
+ 'dated_at' => '',
774
+ 'dated_from' => '',
775
+ 'deja_vu' => deja_vu.to_s,
776
+ 'resource_uuid' => '',
777
+ 'body' => nil,
778
+ 'uri_path_components' => [ 'aa', 'bb' ],
779
+ 'uri_path_extension' => 'xml.gz',
780
+ 'list_offset' => 0,
781
+ 'list_limit' => 50,
782
+ 'list_sort_data' => { 'created_at' => 'desc' },
783
+ 'list_search_data' => {},
784
+ 'list_filter_data' => {},
785
+ 'embeds' => [ 'embed_two' ],
786
+ 'references' => []
787
+ }
788
+ )
789
+ end
790
+
791
+ it 'deletes things with callbacks', :check_callbacks => true do
792
+ delete_things()
793
+ end
794
+
795
+ it 'deletes things without callbacks' do
796
+ delete_things()
797
+ end
798
+
799
+ it 'deletes things, passing through custom locale and deja_vu' do
800
+ delete_things( locale: 'bye', deja_vu: true )
801
+ end
802
+
803
+ it 'deletes things with a simulated 404' do
804
+ response = spec_helper_http(
805
+ klass: Net::HTTP::Delete,
806
+ port: @port,
807
+ path: '/v2/test_some_echoes/simulate_404' )
808
+
809
+ expect( response.code ).to eq( '404' )
810
+ expect_response_headers_on( response )
811
+
812
+ parsed = JSON.parse( response.body )
813
+
814
+ expect( parsed[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'generic.not_found' )
815
+ end
816
+
817
+ it 'deletes things with a 204 with deja vu' do
818
+ response = spec_helper_http(
819
+ klass: Net::HTTP::Delete,
820
+ port: @port,
821
+ path: '/v2/test_some_echoes/simulate_404',
822
+ headers: { 'X-Deja-Vu' => 'yes' }
823
+ )
824
+
825
+ expect( response.code ).to eq( '204' )
826
+ expect( response.body ).to be_nil
827
+ expect( response[ 'X-Deja-Vu' ] ).to eq( 'confirmed' )
828
+ expect_response_headers_on( response )
829
+ end
830
+
831
+ it 'should get 405 for bad requests' do
832
+
833
+ # Attempt a #create (POST with body data) - service only does "show"
834
+ # and "list".
835
+
836
+ response = spec_helper_http(
837
+ klass: Net::HTTP::Post,
838
+ port: @port,
839
+ path: '/v1/test_echo_quiet',
840
+ body: { 'foo' => 'bar', 'baz' => 'boo' }.to_json
841
+ )
842
+
843
+ expect( response.code ).to eq( '405' )
844
+ expect_response_headers_on( response, true ) # true => shouldn't have called service
845
+ end
846
+
847
+ it 'should be detect 404 OK' do
848
+ response = spec_helper_http(
849
+ port: @port,
850
+ path: '/v1/not_present'
851
+ )
852
+
853
+ expect( response.code ).to eq( '404' )
854
+ expect_response_headers_on( response, true ) # true => shouldn't have called service
855
+ end
856
+ end
857
+
858
+ #############################################################################
859
+
860
+ context 'remote inter-resource calls' do
861
+ def app
862
+ Rack::Builder.new do
863
+ use Hoodoo::Services::Middleware
864
+ run TestCallService.new
865
+ end
866
+ end
867
+
868
+ before :example, :check_callbacks => true do
869
+ expect_any_instance_of( TestCallImplementation ).to receive( :before ).once
870
+ expect_any_instance_of( TestEchoImplementation ).to receive( :before ).once
871
+ expect_any_instance_of( TestEchoImplementation ).to receive( :after ).once
872
+ expect_any_instance_of( TestCallImplementation ).to receive( :after ).once
873
+ end
874
+
875
+ def headers_for( locale: nil,
876
+ dated_at: nil,
877
+ dated_from: nil,
878
+ deja_vu: nil,
879
+ resource_uuid: nil )
880
+
881
+ headers = {
882
+ 'HTTP_X_INTERACTION_ID' => @interaction_id,
883
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8'
884
+ }
885
+
886
+ headers[ 'HTTP_CONTENT_LANGUAGE' ] = locale unless locale.nil?
887
+ headers[ 'HTTP_ACCEPT_LANGUAGE' ] = locale unless locale.nil?
888
+ headers[ 'HTTP_X_RESOURCE_UUID' ] = resource_uuid unless resource_uuid.nil?
889
+ headers[ 'HTTP_X_DATED_AT' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_at ) unless dated_at.nil?
890
+ headers[ 'HTTP_X_DATED_FROM' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_from ) unless dated_from.nil?
891
+ headers[ 'HTTP_X_DEJA_VU' ] = 'yes' if deja_vu == true
892
+
893
+ return headers
894
+ end
895
+
896
+ # A helper method for a set of common expectations.
897
+
898
+ def expect_response_options_for( options )
899
+ expect( options ).to_not be_nil
900
+ expect( options[ 'service_response_time' ] ).to_not be_nil
901
+ expect( options[ 'interaction_id' ] ).to_not be_nil
902
+ expect( Hoodoo::UUID.valid?( options[ 'interaction_id' ] ) ).to eq( true )
903
+ end
904
+
905
+ def list_things( locale: nil, dated_at: nil )
906
+ get(
907
+ '/v1/test_call.tar.gz?limit=25&offset=75',
908
+ nil,
909
+ headers_for( locale: locale, dated_at: dated_at )
910
+ )
911
+
912
+ expect( last_response.status ).to eq( 200 )
913
+ parsed = JSON.parse( last_response.body )
914
+
915
+ # Outer calls wrap arrays in object with "_data" key for JSON (since we
916
+ # don't do JSON5 and only JSON5 allows outermost / top-level arrays), but
917
+ # the inter-resource calls unpack that for us, so we should see the outer
918
+ # service's "_data" array nesting directly the inner service's array if
919
+ # the middleware dereferenced it correctly for us.
920
+
921
+ expect( parsed[ '_data' ]).to_not be_nil
922
+ expect( parsed[ '_data' ][ 0 ]).to_not be_nil
923
+ expect( parsed[ '_data' ][ 0 ][ 'listA' ] ).to_not be_nil
924
+ expect( parsed[ '_data' ][ 0 ][ 'listA' ][ 0 ] ).to_not be_nil
925
+ expect( parsed[ '_data' ][ 0 ][ 'listA' ][ 0 ][ 'list0'] ).to eq(
926
+ {
927
+ 'locale' => locale.nil? ? 'en-nz' : locale,
928
+ 'dated_at' => dated_at.to_s,
929
+ 'dated_from' => '',
930
+ 'deja_vu' => '',
931
+ 'resource_uuid' => '',
932
+ 'body' => nil,
933
+ 'uri_path_components' => [],
934
+ 'uri_path_extension' => '',
935
+ 'list_offset' => 75,
936
+ 'list_limit' => 25,
937
+ 'list_sort_data' => { 'created_at' => 'desc' },
938
+ 'list_search_data' => {},
939
+ 'list_filter_data' => {},
940
+ 'embeds' => [],
941
+ 'references' => []
942
+ }
943
+ )
944
+
945
+ expect( parsed[ '_dataset_size' ]).to eq( 51 )
946
+
947
+ expect( parsed[ '_data' ][ 3 ] ).to_not be_nil
948
+ expect_response_options_for( parsed[ '_data' ][ 3 ][ 'options' ] )
949
+ end
950
+
951
+ it 'list things in the remote service with callbacks', :check_callbacks => true do
952
+ list_things()
953
+ end
954
+
955
+ it 'list things in the remote service without callbacks' do
956
+ list_things()
957
+ end
958
+
959
+ it 'lists things with a custom locale and dated-at time' do
960
+ list_things( locale: 'foo', dated_at: DateTime.now )
961
+ end
962
+
963
+ it 'complains if the JSON implementation is not up to scratch' do
964
+ module JSON
965
+ class << self
966
+ def dumb_parse( data, ignored )
967
+ JSON.original_parse( data )
968
+ end
969
+
970
+ alias original_parse parse
971
+ alias parse dumb_parse
972
+ end
973
+ end
974
+
975
+ get(
976
+ '/v1/test_call.tar.gz?limit=25&offset=75',
977
+ nil,
978
+ headers_for( locale: 'de' )
979
+ )
980
+
981
+ module JSON
982
+ class << self
983
+ alias parse original_parse
984
+ end
985
+ end
986
+
987
+ expect( last_response.status ).to eq( 500 )
988
+ expect_response_headers_on( last_response, true ) # true => shouldn't have called service
989
+
990
+ parsed = JSON.parse( last_response.body )
991
+
992
+ expect( parsed[ 'errors' ][ 0 ][ 'message' ] ).to eq( "Hoodoo::Services::Middleware: Incompatible JSON implementation in use which doesn't understand 'object_class' or 'array_class' options" )
993
+ end
994
+
995
+ it 'gets the correct type back when an inter-resource remote call generates an error' do
996
+ expect_any_instance_of( TestCallImplementation ).to receive( :expectable_result_hook ) do | instance, result |
997
+ expect( result ).to be_a( Hoodoo::Client::AugmentedArray )
998
+ expect( result.platform_errors.has_errors? ).to eq( true )
999
+ end
1000
+
1001
+ get(
1002
+ '/v1/test_call.tar.gz?offset=42', # 42 -> magic -> inner service adds error
1003
+ nil,
1004
+ headers_for( locale: 'de' )
1005
+ )
1006
+
1007
+ result = JSON.parse(last_response.body)
1008
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1009
+ end
1010
+
1011
+ def show_things( locale: nil, dated_at: nil )
1012
+ get(
1013
+ '/v1/test_call/one/two.tar.gz',
1014
+ nil,
1015
+ headers_for( locale: locale, dated_at: dated_at )
1016
+ )
1017
+
1018
+ expect( last_response.status ).to eq( 200 )
1019
+ parsed = JSON.parse( last_response.body )
1020
+
1021
+ expect( parsed[ 'show' ]).to_not be_nil
1022
+ expect( parsed[ 'show' ][ 'show' ] ).to eq(
1023
+ {
1024
+ 'locale' => locale.nil? ? 'en-nz' : locale,
1025
+ 'dated_at' => dated_at.to_s,
1026
+ 'dated_from' => '',
1027
+ 'deja_vu' => '',
1028
+ 'resource_uuid' => '',
1029
+ 'body' => nil,
1030
+ 'uri_path_components' => [ 'one,two' ],
1031
+ 'uri_path_extension' => '', # This is the *inner* inter-resource call's state and no filename extensions are used internally
1032
+ 'list_offset' => 0,
1033
+ 'list_limit' => 50,
1034
+ 'list_sort_data' => { 'created_at' => 'desc' },
1035
+ 'list_search_data' => {},
1036
+ 'list_filter_data' => {},
1037
+ 'embeds' => [],
1038
+ 'references' => []
1039
+ }
1040
+ )
1041
+
1042
+ expect( parsed[ 'options' ] ).to_not be_nil
1043
+ expect_response_options_for( parsed[ 'options' ] )
1044
+ end
1045
+
1046
+ it 'shows things in the remote service with callbacks', :check_callbacks => true do
1047
+ show_things()
1048
+ end
1049
+
1050
+ it 'shows things in the remote service without callbacks' do
1051
+ show_things()
1052
+ end
1053
+
1054
+ it 'shows things with a custom locale and dated-at time' do
1055
+ show_things( locale: 'bar', dated_at: DateTime.now )
1056
+ end
1057
+
1058
+ it 'gets the correct type back when an inter-resource remote call generates an error' do
1059
+ expect_any_instance_of( TestCallImplementation ).to receive( :expectable_result_hook ) do | instance, result |
1060
+ expect( result ).to be_a( Hoodoo::Client::AugmentedHash )
1061
+ expect( result.platform_errors.has_errors? ).to eq( true )
1062
+ end
1063
+
1064
+ get( '/v1/test_call/return_error', nil, headers_for() )
1065
+
1066
+ result = JSON.parse(last_response.body)
1067
+ expect(result['errors'][0]['code']).to eq('generic.invalid_string')
1068
+ end
1069
+
1070
+ def create_things( locale: nil, dated_from: nil, deja_vu: nil, resource_uuid: nil )
1071
+ post(
1072
+ '/v1/test_call.tar.gz',
1073
+ { 'foo' => 'bar', 'baz' => 'boo' }.to_json,
1074
+ headers_for( locale: locale, dated_from: dated_from, deja_vu: deja_vu, resource_uuid: resource_uuid )
1075
+ )
1076
+
1077
+ expect( last_response.status ).to eq( 200 )
1078
+ parsed = JSON.parse( last_response.body )
1079
+
1080
+ expect( parsed[ 'create' ]).to_not be_nil
1081
+ expect( parsed[ 'create' ][ 'create' ] ).to eq(
1082
+ {
1083
+ 'locale' => locale.nil? ? 'en-nz' : locale,
1084
+ 'dated_at' => '',
1085
+ 'dated_from' => dated_from.to_s,
1086
+ 'deja_vu' => '', # Not passed through
1087
+ 'resource_uuid' => '', # Not passed through
1088
+ 'body' => { 'foo' => 'bar', 'baz' => 'boo' },
1089
+ 'uri_path_components' => [],
1090
+ 'uri_path_extension' => '',
1091
+ 'list_offset' => 0,
1092
+ 'list_limit' => 50,
1093
+ 'list_sort_data' => { 'created_at' => 'desc' },
1094
+ 'list_search_data' => {},
1095
+ 'list_filter_data' => {},
1096
+ 'embeds' => [],
1097
+ 'references' => []
1098
+ }
1099
+ )
1100
+
1101
+ expect( parsed[ 'options' ] ).to_not be_nil
1102
+ expect_response_options_for( parsed[ 'options' ] )
1103
+ end
1104
+
1105
+ it 'creates things in the remote service with callbacks', :check_callbacks => true do
1106
+ create_things()
1107
+ end
1108
+
1109
+ it 'creates things in the remote service without callbacks' do
1110
+ create_things()
1111
+ end
1112
+
1113
+ it 'creates things with a custom locale, passes through dated_from but not deja_vu' do
1114
+ create_things( locale: 'baz', dated_from: DateTime.now, deja_vu: true )
1115
+ end
1116
+
1117
+ it 'can specify a UUID via an inter-resource call if it has top-level permission' do
1118
+ @test_session.scoping.authorised_http_headers = [ 'HTTP_X_RESOURCE_UUID' ]
1119
+
1120
+ post(
1121
+ '/v1/test_call.tar.gz',
1122
+ { :foo => 'specify_uuid' }.to_json,
1123
+ headers_for()
1124
+ )
1125
+
1126
+ expect( last_response.status ).to eq( 200 )
1127
+ parsed = JSON.parse( last_response.body )
1128
+
1129
+ expect( parsed[ 'create' ]).to_not be_nil
1130
+ resource_uuid = parsed[ 'create' ][ 'create' ][ 'resource_uuid' ]
1131
+ expect( Hoodoo::UUID.valid?( resource_uuid ) ).to eq( true )
1132
+ end
1133
+
1134
+ it 'cannot specify a UUID via an inter-resource call if it does not have top-level permission' do
1135
+ post(
1136
+ '/v1/test_call.tar.gz',
1137
+ { :foo => 'specify_uuid' }.to_json,
1138
+ headers_for()
1139
+ )
1140
+
1141
+ expect( last_response.status ).to eq( 403 )
1142
+ expect_response_headers_on( last_response ) # should call target service, but fails on inter-resource call
1143
+
1144
+ parsed = JSON.parse( last_response.body )
1145
+
1146
+ expect( parsed[ 'errors' ].size ).to eq( 1 )
1147
+ expect( parsed[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
1148
+ expect( parsed[ 'errors' ][ 0 ][ 'reference' ] ).to be_nil # Ensure no information disclosure vulnerability
1149
+ end
1150
+
1151
+ # Remember this time the deja-vu stuff is happening one level down,
1152
+ # as an inter-resource call, not at the top level per the similar
1153
+ # tests that use spec_helper_http in the previous major "section"
1154
+ # of this file.
1155
+ #
1156
+ it 'gets a 204 with deja-vu and duplication' do
1157
+ post(
1158
+ '/v1/test_call.tar.gz',
1159
+ { 'foo' => 'bar', 'deja_vu_in_other_resource' => 'yes' }.to_json,
1160
+ headers_for() # *not* requesting Deja-Vu at the top level
1161
+ )
1162
+
1163
+ # If the test passes, the inter-resource call returned the equivlant
1164
+ # of a 204, so the parsed payload giving us the result of that call
1165
+ # will show an empty Hash and a 'confirmed' option from the endpoint.
1166
+
1167
+ expect( last_response.status ).to eq( 200 )
1168
+ expect_response_headers_on( last_response )
1169
+
1170
+ parsed = JSON.parse( last_response.body )
1171
+
1172
+ expect( parsed[ 'create' ] ).to eq( {} )
1173
+ expect( parsed[ 'options' ][ 'deja_vu' ] ).to eq( 'confirmed' )
1174
+ end
1175
+
1176
+ it 'gets non-204 with deja-vu and duplication plus other errors' do
1177
+ post(
1178
+ '/v1/test_call.tar.gz',
1179
+ {
1180
+ 'foo' => 'bar',
1181
+ 'deja_vu_in_other_resource' => 'yes',
1182
+ 'additional_error' => 'generic.invalid_decimal'
1183
+ }.to_json,
1184
+ headers_for() # *not* requesting Deja-Vu at the top level
1185
+ )
1186
+
1187
+ # If the test passes, the inter-resource call will add other errors
1188
+ # that aren't just duplication, so the call returns the full error
1189
+ # set normally rather than via a 204 response equivalent. Normal
1190
+ # error handling in the calling resource therefore takes place.
1191
+
1192
+ expect( last_response.status ).to eq( 422 )
1193
+ expect_response_headers_on( last_response )
1194
+
1195
+ parsed = JSON.parse( last_response.body )
1196
+
1197
+ expect( parsed[ 'errors' ].size ).to eq( 2 )
1198
+ expect( parsed[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'generic.invalid_duplication' )
1199
+ expect( parsed[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'deja_vu' )
1200
+ expect( parsed[ 'errors' ][ 1 ][ 'code' ] ).to eq( 'generic.invalid_decimal' )
1201
+ expect( parsed[ 'errors' ][ 1 ][ 'reference' ] ).to eq( 'deja_vu' )
1202
+ end
1203
+
1204
+ it 'rejects non-deja-vu 204 responses' do
1205
+ expect_any_instance_of( Hoodoo::Client::Endpoint::HTTPBased::DescriptionOfResponse ).to receive( :http_status_code ).and_return( 204 )
1206
+ expect_any_instance_of( Hoodoo::Client::Endpoint::HTTPBased::DescriptionOfResponse ).to receive( :raw_body_data ).and_return( 'not JSON' )
1207
+
1208
+ post(
1209
+ '/v1/test_call.tar.gz',
1210
+ { 'foo' => 'bar' }.to_json,
1211
+ headers_for()
1212
+ )
1213
+
1214
+ expect( last_response.status ).to eq( 500 )
1215
+ expect_response_headers_on( last_response )
1216
+
1217
+ parsed = JSON.parse( last_response.body )
1218
+
1219
+ expect( parsed[ 'errors' ].size ).to eq( 1 )
1220
+ expect( parsed[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.fault' )
1221
+ expect( parsed[ 'errors' ][ 0 ][ 'message' ] ).to eq( "Unexpected raw HTTP status code 204 with 'X-Deja-Vu: confirmed' not present" )
1222
+ expect( parsed[ 'errors' ][ 0 ][ 'reference' ] ).to eq( '204' )
1223
+ end
1224
+
1225
+ it 'gets the correct type back when an inter-resource remote call generates an error' do
1226
+ expect_any_instance_of( TestCallImplementation ).to receive( :expectable_result_hook ) do | instance, result |
1227
+ expect( result ).to be_a( Hoodoo::Client::AugmentedHash )
1228
+ expect( result.platform_errors.has_errors? ).to eq( true )
1229
+ end
1230
+
1231
+ post(
1232
+ '/v1/test_call.tar.gz',
1233
+ { :foo => 'bar', :return_error => 'yes' }.to_json,
1234
+ headers_for()
1235
+ )
1236
+
1237
+ result = JSON.parse(last_response.body)
1238
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1239
+ end
1240
+
1241
+ def update_things( locale: nil )
1242
+ patch(
1243
+ '/v1/test_call/aa/bb.tar.gz',
1244
+ { 'foo' => 'boo', 'baz' => 'bar' }.to_json,
1245
+ headers_for( locale: locale )
1246
+ )
1247
+
1248
+ expect( last_response.status ).to eq( 200 )
1249
+ parsed = JSON.parse( last_response.body )
1250
+
1251
+ expect( parsed[ 'update' ]).to_not be_nil
1252
+ expect( parsed[ 'update' ][ 'update' ] ).to eq(
1253
+ {
1254
+ 'locale' => locale.nil? ? 'en-nz' : locale,
1255
+ 'dated_at' => '',
1256
+ 'dated_from' => '',
1257
+ 'deja_vu' => '',
1258
+ 'resource_uuid' => '',
1259
+ 'body' => { 'foo' => 'boo', 'baz' => 'bar' },
1260
+ 'uri_path_components' => [ 'aa,bb' ],
1261
+ 'uri_path_extension' => '',
1262
+ 'list_offset' => 0,
1263
+ 'list_limit' => 50,
1264
+ 'list_sort_data' => { 'created_at' => 'desc' },
1265
+ 'list_search_data' => {},
1266
+ 'list_filter_data' => {},
1267
+ 'embeds' => [],
1268
+ 'references' => []
1269
+ }
1270
+ )
1271
+
1272
+ expect( parsed[ 'options' ] ).to_not be_nil
1273
+ expect_response_options_for( parsed[ 'options' ] )
1274
+ expect( parsed[ 'options' ][ 'example_header' ] ).to eq( 'example' )
1275
+ end
1276
+
1277
+ it 'updates things in the remote service with callbacks', :check_callbacks => true do
1278
+ update_things()
1279
+ end
1280
+
1281
+ it 'updates things in the remote service without callbacks' do
1282
+ update_things()
1283
+ end
1284
+
1285
+ it 'updates things with a custom locale' do
1286
+ update_things( locale: 'boo' )
1287
+ end
1288
+
1289
+ it 'gets the correct type back when an inter-resource remote call generates an error' do
1290
+ expect_any_instance_of( TestCallImplementation ).to receive( :expectable_result_hook ) do | instance, result |
1291
+ expect( result ).to be_a( Hoodoo::Client::AugmentedHash )
1292
+ expect( result.platform_errors.has_errors? ).to eq( true )
1293
+ end
1294
+
1295
+ patch( '/v1/test_call/return_error', '{}', headers_for() )
1296
+
1297
+ result = JSON.parse(last_response.body)
1298
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1299
+ end
1300
+
1301
+ def delete_things( locale: nil, deja_vu: nil )
1302
+ delete(
1303
+ '/v1/test_call/aone/btwo.tar.gz',
1304
+ nil,
1305
+ headers_for( locale:locale, deja_vu: deja_vu )
1306
+ )
1307
+
1308
+ expect( last_response.status ).to eq( 200 )
1309
+ parsed = JSON.parse( last_response.body )
1310
+
1311
+ expect( parsed[ 'delete' ]).to_not be_nil
1312
+ expect( parsed[ 'delete' ][ 'delete' ] ).to eq(
1313
+ {
1314
+ 'locale' => locale.nil? ? 'en-nz' : locale,
1315
+ 'dated_at' => '',
1316
+ 'dated_from' => '',
1317
+ 'deja_vu' => '', # Not passed through
1318
+ 'resource_uuid' => '', # Not passed through
1319
+ 'body' => nil,
1320
+ 'uri_path_components' => [ 'aone,btwo' ],
1321
+ 'uri_path_extension' => '',
1322
+ 'list_offset' => 0,
1323
+ 'list_limit' => 50,
1324
+ 'list_sort_data' => { 'created_at' => 'desc' },
1325
+ 'list_search_data' => {},
1326
+ 'list_filter_data' => {},
1327
+ 'embeds' => [],
1328
+ 'references' => []
1329
+ }
1330
+ )
1331
+
1332
+ expect( parsed[ 'options' ] ).to_not be_nil
1333
+ expect_response_options_for( parsed[ 'options' ] )
1334
+ end
1335
+
1336
+ it 'deletes things in the remote service with callbacks', :check_callbacks => true do
1337
+ delete_things()
1338
+ end
1339
+
1340
+ it 'deletes things in the remote service without callbacks' do
1341
+ delete_things()
1342
+ end
1343
+
1344
+ it 'deletes things, passing through custom locale but not deja_v' do
1345
+ delete_things( locale: 'bye', deja_vu: true )
1346
+ end
1347
+
1348
+ it 'should receive errors from remote service as if from the local call' do
1349
+ get(
1350
+ '/v1/test_call/return_error',
1351
+ nil,
1352
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1353
+ )
1354
+
1355
+ expect( last_response.status ).to eq( 422 )
1356
+ expect_response_headers_on( last_response )
1357
+
1358
+ parsed = JSON.parse( last_response.body )
1359
+
1360
+ expect( parsed[ 'errors' ] ).to_not be_nil
1361
+ expect( parsed[ 'errors' ].count ).to eq(1)
1362
+ expect( parsed[ 'errors' ][ 0 ] ).to eq({
1363
+ 'code' => 'generic.invalid_string',
1364
+ 'message' => 'Returning error as requested',
1365
+ 'reference' => 'no ident,no other ident'
1366
+ })
1367
+ end
1368
+
1369
+ it 'gets a 404 for missing endpoints (and gets correct type back when inter-resource call generates an error)' do
1370
+ expect_any_instance_of( TestCallImplementation ).to receive( :expectable_result_hook ) do | instance, result |
1371
+ expect( result ).to be_a( Hoodoo::Client::AugmentedHash )
1372
+ expect( result.platform_errors.has_errors? ).to eq( true )
1373
+ end
1374
+
1375
+ get(
1376
+ '/v1/test_call/generate_404',
1377
+ nil,
1378
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1379
+ )
1380
+
1381
+ expect( last_response.status ).to eq( 404 )
1382
+ expect_response_headers_on( last_response )
1383
+ end
1384
+
1385
+ it 'can reuse an endpoint' do
1386
+ get(
1387
+ '/v1/test_call/ensure_repeated_use_works',
1388
+ nil,
1389
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1390
+ )
1391
+
1392
+ expect( last_response.status ).to eq( 200 )
1393
+ expect_response_headers_on( last_response )
1394
+
1395
+ parsed = JSON.parse( last_response.body )
1396
+
1397
+ expect( parsed[ 'show' ]).to_not be_nil
1398
+ expect( parsed[ 'show' ][ 'show' ][ 'uri_path_components' ] ).to eq( [ 'ensure_repeated_use_works' ] )
1399
+
1400
+ expect( parsed[ 'options' ] ).to_not be_nil
1401
+ expect_response_options_for( parsed[ 'options' ] )
1402
+ end
1403
+
1404
+ # Ruby can't kill off an "unresponsive" thread - there seems to be no
1405
+ # equivalent of "kill -9" and the likes of "#exit!" are long gone - so
1406
+ # the WEBrick server thread, which never returns to the Ruby interpreter
1407
+ # after the Rack::Server.start() call (or equivalent) can't die. Instead
1408
+ # we are forced to write a fragile test that simulates a connection
1409
+ # failure to the endpoint.
1410
+ #
1411
+ it 'should get a 404 for no-longer-running endpoints' do
1412
+ expect_any_instance_of( Net::HTTP ).to receive( :request ).once.and_raise( Errno::ECONNREFUSED )
1413
+
1414
+ get(
1415
+ '/v1/test_call/show_something',
1416
+ nil,
1417
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1418
+ )
1419
+
1420
+ expect( last_response.status ).to eq( 404 )
1421
+ expect_response_headers_on( last_response ) # should call target service, but fails on inter-resource call
1422
+ end
1423
+
1424
+ # Similarly shaky test for simulating arbitrary other failure kinds.
1425
+ #
1426
+ it 'should get a 500 for arbitrary failures' do
1427
+ expect_any_instance_of( Net::HTTP ).to receive( :request ).once.and_raise( 'some connection error' )
1428
+
1429
+ get(
1430
+ '/v1/test_call/show_something',
1431
+ nil,
1432
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1433
+ )
1434
+
1435
+ expect( last_response.status ).to eq( 500 )
1436
+ expect( last_response.body ).to include( 'some connection error' )
1437
+ expect_response_headers_on( last_response )
1438
+ end
1439
+ end
1440
+ end