hoodoo 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hoodoo +5 -0
  3. data/lib/hoodoo.rb +27 -0
  4. data/lib/hoodoo/active.rb +32 -0
  5. data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
  6. data/lib/hoodoo/active/active_record/base.rb +81 -0
  7. data/lib/hoodoo/active/active_record/creator.rb +134 -0
  8. data/lib/hoodoo/active/active_record/dated.rb +343 -0
  9. data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
  10. data/lib/hoodoo/active/active_record/finder.rb +606 -0
  11. data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
  12. data/lib/hoodoo/active/active_record/secure.rb +431 -0
  13. data/lib/hoodoo/active/active_record/support.rb +106 -0
  14. data/lib/hoodoo/active/active_record/translated.rb +87 -0
  15. data/lib/hoodoo/active/active_record/uuid.rb +80 -0
  16. data/lib/hoodoo/active/active_record/writer.rb +321 -0
  17. data/lib/hoodoo/client.rb +23 -0
  18. data/lib/hoodoo/client/augmented_array.rb +29 -0
  19. data/lib/hoodoo/client/augmented_base.rb +168 -0
  20. data/lib/hoodoo/client/augmented_hash.rb +23 -0
  21. data/lib/hoodoo/client/client.rb +354 -0
  22. data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
  23. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
  24. data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
  25. data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
  26. data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
  27. data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
  28. data/lib/hoodoo/client/headers.rb +269 -0
  29. data/lib/hoodoo/communicators.rb +23 -0
  30. data/lib/hoodoo/communicators/fast.rb +44 -0
  31. data/lib/hoodoo/communicators/pool.rb +601 -0
  32. data/lib/hoodoo/communicators/slow.rb +84 -0
  33. data/lib/hoodoo/data.rb +51 -0
  34. data/lib/hoodoo/data/resources/caller.rb +39 -0
  35. data/lib/hoodoo/data/resources/errors.rb +28 -0
  36. data/lib/hoodoo/data/resources/log.rb +31 -0
  37. data/lib/hoodoo/data/resources/session.rb +26 -0
  38. data/lib/hoodoo/data/types/error_primitive.rb +27 -0
  39. data/lib/hoodoo/data/types/permissions.rb +40 -0
  40. data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
  41. data/lib/hoodoo/data/types/permissions_full.rb +28 -0
  42. data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
  43. data/lib/hoodoo/discovery.rb +20 -0
  44. data/lib/hoodoo/errors.rb +19 -0
  45. data/lib/hoodoo/errors/error_descriptions.rb +229 -0
  46. data/lib/hoodoo/errors/errors.rb +322 -0
  47. data/lib/hoodoo/generator.rb +139 -0
  48. data/lib/hoodoo/logger.rb +23 -0
  49. data/lib/hoodoo/logger/fast_writer.rb +27 -0
  50. data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
  51. data/lib/hoodoo/logger/logger.rb +387 -0
  52. data/lib/hoodoo/logger/slow_writer.rb +49 -0
  53. data/lib/hoodoo/logger/writer_mixin.rb +52 -0
  54. data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
  55. data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
  56. data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
  57. data/lib/hoodoo/middleware.rb +33 -0
  58. data/lib/hoodoo/presenters.rb +45 -0
  59. data/lib/hoodoo/presenters/base.rb +281 -0
  60. data/lib/hoodoo/presenters/base_dsl.rb +519 -0
  61. data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
  62. data/lib/hoodoo/presenters/embedding.rb +232 -0
  63. data/lib/hoodoo/presenters/types/array.rb +118 -0
  64. data/lib/hoodoo/presenters/types/boolean.rb +26 -0
  65. data/lib/hoodoo/presenters/types/date.rb +26 -0
  66. data/lib/hoodoo/presenters/types/date_time.rb +26 -0
  67. data/lib/hoodoo/presenters/types/decimal.rb +47 -0
  68. data/lib/hoodoo/presenters/types/enum.rb +55 -0
  69. data/lib/hoodoo/presenters/types/field.rb +158 -0
  70. data/lib/hoodoo/presenters/types/float.rb +26 -0
  71. data/lib/hoodoo/presenters/types/hash.rb +361 -0
  72. data/lib/hoodoo/presenters/types/integer.rb +26 -0
  73. data/lib/hoodoo/presenters/types/object.rb +117 -0
  74. data/lib/hoodoo/presenters/types/string.rb +53 -0
  75. data/lib/hoodoo/presenters/types/tags.rb +24 -0
  76. data/lib/hoodoo/presenters/types/text.rb +26 -0
  77. data/lib/hoodoo/presenters/types/uuid.rb +54 -0
  78. data/lib/hoodoo/services.rb +34 -0
  79. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
  80. data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
  81. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
  82. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
  83. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
  84. data/lib/hoodoo/services/discovery/discovery.rb +186 -0
  85. data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
  86. data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
  87. data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
  88. data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
  89. data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
  90. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
  91. data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
  92. data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
  93. data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
  94. data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
  95. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
  96. data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
  97. data/lib/hoodoo/services/middleware/interaction.rb +127 -0
  98. data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
  99. data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
  100. data/lib/hoodoo/services/services/context.rb +153 -0
  101. data/lib/hoodoo/services/services/implementation.rb +132 -0
  102. data/lib/hoodoo/services/services/interface.rb +934 -0
  103. data/lib/hoodoo/services/services/permissions.rb +250 -0
  104. data/lib/hoodoo/services/services/request.rb +189 -0
  105. data/lib/hoodoo/services/services/response.rb +316 -0
  106. data/lib/hoodoo/services/services/service.rb +141 -0
  107. data/lib/hoodoo/services/services/session.rb +729 -0
  108. data/lib/hoodoo/utilities.rb +12 -0
  109. data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
  110. data/lib/hoodoo/utilities/utilities.rb +380 -0
  111. data/lib/hoodoo/utilities/uuid.rb +44 -0
  112. data/lib/hoodoo/version.rb +17 -0
  113. data/spec/active/active_record/base_spec.rb +57 -0
  114. data/spec/active/active_record/creator_spec.rb +88 -0
  115. data/spec/active/active_record/dated_spec.rb +248 -0
  116. data/spec/active/active_record/error_mapping_spec.rb +360 -0
  117. data/spec/active/active_record/finder_spec.rb +744 -0
  118. data/spec/active/active_record/search_helper_spec.rb +384 -0
  119. data/spec/active/active_record/secure_spec.rb +435 -0
  120. data/spec/active/active_record/support_spec.rb +225 -0
  121. data/spec/active/active_record/translated_spec.rb +19 -0
  122. data/spec/active/active_record/uuid_spec.rb +72 -0
  123. data/spec/active/active_record/writer_spec.rb +272 -0
  124. data/spec/alchemy/alchemy-amq.rb +33 -0
  125. data/spec/client/augmented_array_spec.rb +15 -0
  126. data/spec/client/augmented_base_spec.rb +50 -0
  127. data/spec/client/augmented_hash_spec.rb +15 -0
  128. data/spec/client/client_spec.rb +955 -0
  129. data/spec/client/endpoint/endpoint_spec.rb +70 -0
  130. data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
  131. data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
  132. data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
  133. data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
  134. data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
  135. data/spec/client/headers_spec.rb +172 -0
  136. data/spec/communicators/fast_spec.rb +9 -0
  137. data/spec/communicators/pool_spec.rb +339 -0
  138. data/spec/communicators/slow_spec.rb +15 -0
  139. data/spec/data/resources/caller_spec.rb +156 -0
  140. data/spec/data/resources/errors_spec.rb +22 -0
  141. data/spec/data/resources/log_spec.rb +20 -0
  142. data/spec/data/resources/session_spec.rb +15 -0
  143. data/spec/data/types/error_primitive_spec.rb +15 -0
  144. data/spec/data/types/permissions_defaults_spec.rb +25 -0
  145. data/spec/data/types/permissions_full_spec.rb +44 -0
  146. data/spec/data/types/permissions_resources_spec.rb +34 -0
  147. data/spec/data/types/permissions_spec.rb +37 -0
  148. data/spec/errors/error_descriptions_spec.rb +98 -0
  149. data/spec/errors/errors_spec.rb +346 -0
  150. data/spec/integration/service_actions_spec.rb +112 -0
  151. data/spec/logger/fast_writer_spec.rb +18 -0
  152. data/spec/logger/logger_spec.rb +259 -0
  153. data/spec/logger/slow_writer_spec.rb +144 -0
  154. data/spec/logger/writers/file_writer_spec.rb +37 -0
  155. data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
  156. data/spec/logger/writers/stream_writer_spec.rb +38 -0
  157. data/spec/presenters/base_dsl_spec.rb +111 -0
  158. data/spec/presenters/base_spec.rb +871 -0
  159. data/spec/presenters/common_resource_fields_spec.rb +30 -0
  160. data/spec/presenters/embedding_spec.rb +87 -0
  161. data/spec/presenters/types/array_spec.rb +249 -0
  162. data/spec/presenters/types/boolean_spec.rb +51 -0
  163. data/spec/presenters/types/date_spec.rb +57 -0
  164. data/spec/presenters/types/date_time_spec.rb +59 -0
  165. data/spec/presenters/types/decimal_spec.rb +58 -0
  166. data/spec/presenters/types/enum_spec.rb +71 -0
  167. data/spec/presenters/types/field_spec.rb +77 -0
  168. data/spec/presenters/types/float_spec.rb +50 -0
  169. data/spec/presenters/types/hash_spec.rb +1069 -0
  170. data/spec/presenters/types/integer_spec.rb +50 -0
  171. data/spec/presenters/types/object_spec.rb +177 -0
  172. data/spec/presenters/types/string_spec.rb +65 -0
  173. data/spec/presenters/types/tags_spec.rb +56 -0
  174. data/spec/presenters/types/text_spec.rb +50 -0
  175. data/spec/presenters/types/uuid_spec.rb +46 -0
  176. data/spec/presenters/walk_spec.rb +198 -0
  177. data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
  178. data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
  179. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
  180. data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
  181. data/spec/services/discovery/discovery_spec.rb +73 -0
  182. data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
  183. data/spec/services/discovery/results/for_http_spec.rb +37 -0
  184. data/spec/services/discovery/results/for_local_spec.rb +21 -0
  185. data/spec/services/discovery/results/for_remote_spec.rb +15 -0
  186. data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
  187. data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
  188. data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
  189. data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
  190. data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
  191. data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
  192. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
  193. data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
  194. data/spec/services/middleware/middleware_cors_spec.rb +93 -0
  195. data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
  196. data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
  197. data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
  198. data/spec/services/middleware/middleware_logging_spec.rb +356 -0
  199. data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
  200. data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
  201. data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
  202. data/spec/services/middleware/middleware_public_spec.rb +238 -0
  203. data/spec/services/middleware/middleware_spec.rb +1569 -0
  204. data/spec/services/middleware/string_inquirer_spec.rb +30 -0
  205. data/spec/services/services/application_spec.rb +74 -0
  206. data/spec/services/services/context_spec.rb +48 -0
  207. data/spec/services/services/implementation_spec.rb +45 -0
  208. data/spec/services/services/interface_spec.rb +262 -0
  209. data/spec/services/services/permissions_spec.rb +249 -0
  210. data/spec/services/services/request_spec.rb +95 -0
  211. data/spec/services/services/response_spec.rb +250 -0
  212. data/spec/services/services/session_spec.rb +432 -0
  213. data/spec/spec_helper.rb +298 -0
  214. data/spec/utilities/utilities_spec.rb +537 -0
  215. data/spec/utilities/uuid_spec.rb +20 -0
  216. metadata +615 -0
@@ -0,0 +1,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