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,356 @@
1
+ # service_middleware_spec.rb is too large. This file covers logging
2
+ # extentions around StructuredLogger and AMQPLogMessage.
3
+
4
+ require 'spec_helper'
5
+
6
+ class TestLogImplementation < Hoodoo::Services::Implementation
7
+ def show( context )
8
+ context.response.body = { 'show' => 'the thing', 'the_thing' => context.request.ident }
9
+ end
10
+
11
+ def create( context )
12
+ context.response.set_resource( { 'log' => 'create' } )
13
+ end
14
+
15
+ def update( context )
16
+ context.response.set_resource( { 'log' => 'update' } )
17
+ end
18
+ end
19
+
20
+ class TestLogInterface < Hoodoo::Services::Interface
21
+ interface :TestLog do
22
+ endpoint :test_log, TestLogImplementation
23
+ actions :show, :create, :update
24
+
25
+ secure_log_for :create => :request,
26
+ :update => :response
27
+ end
28
+ end
29
+
30
+ class TestLogService < Hoodoo::Services::Service
31
+ comprised_of TestLogInterface
32
+ end
33
+
34
+ class TestLogInterfaceB < Hoodoo::Services::Interface
35
+ interface :TestLogB do
36
+ version 2
37
+ endpoint :test_log_b, TestLogImplementation
38
+ actions :show, :create, :update
39
+
40
+ secure_log_for :create => :both
41
+ end
42
+ end
43
+
44
+ class TestLogService < Hoodoo::Services::Service
45
+ comprised_of TestLogInterfaceB
46
+ end
47
+
48
+ # Force the middleware logging mode to that passed as a string in 'test_env'.
49
+ # You must have an 'after' block which restores normal test logging if you
50
+ # use this, else other tests may subsequently fail. Returns the log writer
51
+ # instances now in use as an array (Hoodoo::Logger#instances).
52
+ #
53
+ def force_logging_to( test_env )
54
+ Hoodoo::Services::Middleware.class_variable_set( '@@environment', Hoodoo::StringInquirer.new( test_env ) )
55
+ Hoodoo::Services::Middleware.class_variable_set( '@@external_logger', false )
56
+ Hoodoo::Services::Middleware.send( :set_up_basic_logging )
57
+ Hoodoo::Services::Middleware.send( :add_file_logging, File.join( File.dirname( __FILE__), '..', '..', 'log' ) )
58
+ return Hoodoo::Services::Middleware.logger.instances
59
+ end
60
+
61
+ describe Hoodoo::Services::Middleware do
62
+
63
+ before :each do
64
+ @old_env = Hoodoo::Services::Middleware::class_variable_get( '@@environment' )
65
+ @old_logger = Hoodoo::Services::Middleware::logger
66
+ end
67
+
68
+ after :each do
69
+ Hoodoo::Services::Middleware::logger.wait()
70
+ force_logging_to( 'test' )
71
+ Hoodoo::Services::Middleware.class_variable_set( '@@environment', @old_env )
72
+ Hoodoo::Services::Middleware.class_variable_set( '@@logger', @old_logger )
73
+ begin
74
+ Hoodoo::Services::Middleware.remove_class_variable( '@@alchemy' )
75
+ rescue
76
+ end
77
+ end
78
+
79
+ context 'custom loggers' do
80
+ before :each do
81
+ @custom = Hoodoo::Logger.new
82
+ Hoodoo::Services::Middleware.set_logger( @custom )
83
+ end
84
+
85
+ it 'sets a custom logger' do
86
+ expect( Hoodoo::Services::Middleware.logger ).to eq( @custom )
87
+ end
88
+
89
+ it 'complains about bad custom loggers' do
90
+ expect {
91
+ Hoodoo::Services::Middleware.set_logger( Object )
92
+ }.to raise_error( RuntimeError, "Hoodoo::Communicators::set_logger must be called with an instance of Hoodoo::Logger only" )
93
+ end
94
+
95
+ it 'does not add other writers' do
96
+ Hoodoo::Services::Middleware.set_log_folder( '/foo' )
97
+ Hoodoo::Services::Middleware.set_log_folder( '/bar' )
98
+ expect( @custom.instances ).to be_empty
99
+ end
100
+ end
101
+
102
+ context 'off queue' do
103
+ before :each do
104
+ @old_queue = ENV[ 'AMQ_ENDPOINT' ]
105
+ ENV[ 'AMQ_ENDPOINT' ] = nil
106
+
107
+ @cvar = false
108
+ if Hoodoo::Services::Middleware.class_variable_defined?( '@@alchemy' )
109
+ @cvar = true
110
+ @cvar_val = Hoodoo::Services::Middleware.class_variable_get( '@@alchemy' )
111
+ end
112
+ end
113
+
114
+ after :each do
115
+ ENV[ 'AMQ_ENDPOINT' ] = @old_queue
116
+
117
+ if Hoodoo::Services::Middleware.class_variable_defined?( '@@alchemy' )
118
+ if @cvar == true
119
+ Hoodoo::Services::Middleware.class_variable_set( '@@alchemy', @cvar_val )
120
+ else
121
+ Hoodoo::Services::Middleware.remove_class_variable( '@@alchemy' )
122
+ end
123
+ end
124
+ end
125
+
126
+ def app
127
+ Rack::Builder.new do
128
+ use Hoodoo::Services::Middleware
129
+ run TestLogService.new
130
+ end
131
+ end
132
+
133
+ it 'has the expected "test" mode loggers' do
134
+ instances = force_logging_to( 'test' )
135
+
136
+ expect( instances[ 0 ] ).to be_a( Hoodoo::Logger::FileWriter )
137
+ expect( Hoodoo::Services::Middleware.logger.level ).to eq( :debug )
138
+ end
139
+
140
+ it 'has the expected "development" mode loggers' do
141
+ instances = force_logging_to( 'development' )
142
+
143
+ expect( instances[ 0 ] ).to be_a( Hoodoo::Logger::StreamWriter )
144
+ expect( instances[ 1 ] ).to be_a( Hoodoo::Logger::FileWriter )
145
+ expect( Hoodoo::Services::Middleware.logger.level ).to eq( :debug )
146
+ end
147
+
148
+ it 'has the expected "production" mode loggers' do
149
+ instances = force_logging_to( 'production' )
150
+
151
+ expect( instances[ 0 ] ).to be_a( Hoodoo::Logger::FileWriter )
152
+ expect( Hoodoo::Services::Middleware.logger.level ).to eq( :info )
153
+ end
154
+ end
155
+
156
+ context 'on queue' do
157
+ before :each do
158
+ @old_queue = ENV[ 'AMQ_ENDPOINT' ]
159
+ ENV[ 'AMQ_ENDPOINT' ] = 'amqp://test:test@127.0.0.1'
160
+
161
+ @cvar = false
162
+ if Hoodoo::Services::Middleware.class_variable_defined?( '@@alchemy' )
163
+ @cvar = true
164
+ @cvar_val = Hoodoo::Services::Middleware.class_variable_get( '@@alchemy' )
165
+ end
166
+ end
167
+
168
+ after :each do
169
+ ENV[ 'AMQ_ENDPOINT' ] = @old_queue
170
+
171
+ if Hoodoo::Services::Middleware.class_variable_defined?( '@@alchemy' )
172
+ if @cvar == true
173
+ Hoodoo::Services::Middleware.class_variable_set( '@@alchemy', @cvar_val )
174
+ else
175
+ Hoodoo::Services::Middleware.remove_class_variable( '@@alchemy' )
176
+ end
177
+ end
178
+ end
179
+
180
+ class FakeAlchemy
181
+ def initialize(app)
182
+ @app = app
183
+ end
184
+ def call(env)
185
+ env['rack.alchemy'] = self
186
+ @app.call(env)
187
+ end
188
+ def send_message(*args)
189
+ end
190
+ end
191
+
192
+ def app
193
+ Rack::Builder.new do
194
+ use FakeAlchemy
195
+ use Hoodoo::Services::Middleware
196
+ run TestLogService.new
197
+ end
198
+ end
199
+
200
+ # In these tests, the logger instance array isn't complete until at least
201
+ # one call has gone through the middleware, providing an Alchemy endpoint
202
+ # and allowing the on-queue logger to be added.
203
+
204
+ it 'has the expected "test" mode loggers' do
205
+ force_logging_to( 'test' )
206
+
207
+ get '/v1/test_log/hello', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
208
+
209
+ instances = Hoodoo::Services::Middleware.logger.instances
210
+ expect( instances[ 0 ] ).to be_a( Hoodoo::Logger::FileWriter )
211
+ expect( Hoodoo::Services::Middleware.logger.level ).to eq( :debug )
212
+ end
213
+
214
+ it 'has the expected "development" mode loggers' do
215
+ force_logging_to( 'development' )
216
+
217
+ expect_any_instance_of(FakeAlchemy).to receive(:send_message).at_least(:once)
218
+ spec_helper_silence_stdout() do
219
+ get '/v1/test_log/hello', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
220
+ end
221
+
222
+ instances = Hoodoo::Services::Middleware.logger.instances
223
+ expect( instances[ 0 ] ).to be_a( Hoodoo::Logger::StreamWriter )
224
+ expect( instances[ 1 ] ).to be_a( Hoodoo::Services::Middleware::AMQPLogWriter )
225
+ expect( Hoodoo::Services::Middleware.logger.level ).to eq( :debug )
226
+ end
227
+
228
+ it 'has the expected "production" mode loggers' do
229
+ force_logging_to( 'production' )
230
+
231
+ expect_any_instance_of(FakeAlchemy).to receive(:send_message).at_least(:once)
232
+ get '/v1/test_log/hello', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
233
+
234
+ instances = Hoodoo::Services::Middleware.logger.instances
235
+ expect( instances[ 0 ] ).to be_a( Hoodoo::Services::Middleware::AMQPLogWriter )
236
+ expect( Hoodoo::Services::Middleware.logger.level ).to eq( :info )
237
+ end
238
+ end
239
+
240
+ context 'secure logging' do
241
+ class HashLogger < Hoodoo::Logger::FastWriter
242
+ @@log_data = []
243
+
244
+ def self.reset
245
+ @@log_data = []
246
+ end
247
+
248
+ def self.log_data
249
+ @@log_data
250
+ end
251
+
252
+ def report( log_level, component, code, data )
253
+ @@log_data << { log_level: log_level, component: component, code: code, data: data }
254
+ end
255
+ end
256
+
257
+ def get_data_for( action )
258
+ inbound = HashLogger.log_data().select { | x | x[ :code ] == :inbound }
259
+ result = HashLogger.log_data().select { | x | x[ :code ] == "middleware_#{ action }" }
260
+ outbound = HashLogger.log_data().select { | x | x[ :code ] == :outbound }
261
+
262
+ expect( inbound ).to_not be_empty
263
+ expect( result ).to_not be_empty
264
+ expect( outbound ).to_not be_empty
265
+
266
+ return [ inbound, result, outbound ]
267
+ end
268
+
269
+ before :each do
270
+ HashLogger.reset()
271
+ logger = Hoodoo::Logger.new
272
+ logger.add( HashLogger.new )
273
+ Hoodoo::Services::Middleware.set_logger( logger )
274
+ end
275
+
276
+ def app
277
+ Rack::Builder.new do
278
+ use Hoodoo::Services::Middleware
279
+ run TestLogService.new
280
+ end
281
+ end
282
+
283
+ def check_common_entries( resource, version, action, *args )
284
+ args.each do | entry |
285
+ expect( entry ).to have_key( :log_level )
286
+ expect( entry ).to have_key( :code )
287
+ expect( entry ).to have_key( :component )
288
+ expect( entry ).to have_key( :data )
289
+
290
+ expect( entry[ :data ] ).to have_key( :interaction_id )
291
+ expect( entry[ :data ] ).to have_key( :session )
292
+ expect( entry[ :data ] ).to have_key( :target )
293
+
294
+ expect( entry[ :data ][ :target ] ).to have_key( :resource )
295
+ expect( entry[ :data ][ :target ] ).to have_key( :version )
296
+ expect( entry[ :data ][ :target ] ).to have_key( :action )
297
+
298
+ expect( entry[ :data ][ :target ][ :resource ] ).to eq( resource )
299
+ expect( entry[ :data ][ :target ][ :version ] ).to eq( version )
300
+ expect( entry[ :data ][ :target ][ :action ] ).to eq( action )
301
+ end
302
+ end
303
+
304
+ # To test_log, 'create' says secure for 'request'
305
+ #
306
+ it 'does not log creation requests unexpectedly' do
307
+ post '/v1/test_log', '{ "foo": "bar" }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
308
+
309
+ inbound, result, outbound = get_data_for( :create )
310
+ check_common_entries( 'TestLog', 1, 'create', inbound.last, result.first, outbound.first )
311
+
312
+ entry = inbound.last; expect( entry[ :data ][ :payload ] ).to_not have_key( :body )
313
+ entry = result.first; expect( entry[ :data ] ).to have_key( :payload )
314
+ entry = outbound.first; expect( entry[ :data ][ :payload ] ).to have_key( :response_body )
315
+ end
316
+
317
+ # To test_log, 'update' says secure for 'request'
318
+ #
319
+ it 'does not log update responses unexpectedly' do
320
+ patch '/v1/test_log/foo', '{ "foo": "bar" }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
321
+
322
+ inbound, result, outbound = get_data_for( :update )
323
+ check_common_entries( 'TestLog', 1, 'update', inbound.last, result.first, outbound.first )
324
+
325
+ entry = inbound.last; expect( entry[ :data ][ :payload ] ).to have_key( :body )
326
+ entry = result.first; expect( entry[ :data ] ).to_not have_key( :payload )
327
+ entry = outbound.first; expect( entry[ :data ][ :payload ] ).to_not have_key( :response_body )
328
+ end
329
+
330
+ # To test_log_b, 'create' says secure for 'both'.
331
+ #
332
+ it 'does not log requests or responses unexpectedly' do
333
+ post '/v2/test_log_b', '{ "foo": "bar" }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
334
+
335
+ inbound, result, outbound = get_data_for( :create )
336
+ check_common_entries( 'TestLogB', 2, 'create', inbound.last, result.first, outbound.first )
337
+
338
+ entry = inbound.last; expect( entry[ :data ][ :payload ] ).to_not have_key( :body )
339
+ entry = result.first; expect( entry[ :data ] ).to_not have_key( :payload )
340
+ entry = outbound.first; expect( entry[ :data ][ :payload ] ).to_not have_key( :response_body )
341
+ end
342
+
343
+ # To test_log_b, 'update' does not ask for security.
344
+ #
345
+ it 'does not log requests or responses unexpectedly' do
346
+ patch '/v2/test_log_b/foo', '{ "foo": "bar" }', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
347
+
348
+ inbound, result, outbound = get_data_for( :update )
349
+ check_common_entries( 'TestLogB', 2, 'update', inbound.last, result.first, outbound.first )
350
+
351
+ entry = inbound.last; expect( entry[ :data ][ :payload ] ).to have_key( :body )
352
+ entry = result.first; expect( entry[ :data ] ).to have_key( :payload )
353
+ entry = outbound.first; expect( entry[ :data ][ :payload ] ).to have_key( :response_body )
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,1094 @@
1
+ ###############################################################################
2
+ # Local inter-resource local calls
3
+ ###############################################################################
4
+
5
+ require 'spec_helper'
6
+
7
+ # This gets inter-resource called from ...BImplementation. It expects search
8
+ # data containing an 'offset' key and string/integer value. If > 0, an error
9
+ # is triggered quoting the offset value in the reference data; else a hook
10
+ # method is called that we can check with RSpec.
11
+ #
12
+ # It contains one public action, to test public-to-public calling from ...B.
13
+
14
+ class RSpecTestInterResourceCallsAImplementation < Hoodoo::Services::Implementation
15
+
16
+ def list( context )
17
+ search_offset = ( ( context.request.list.search_data || {} )[ 'offset' ] || '0' ).to_i
18
+
19
+ if search_offset > 0
20
+ context.response.add_error( 'service_calls_a.triggered', 'reference' => { :offset => search_offset } )
21
+ else
22
+ context.response.set_resources( [1,2,3,4], 4321 )
23
+ expectable_hook( context )
24
+ end
25
+ end
26
+
27
+ def show( context )
28
+ expectable_hook( context )
29
+
30
+ if context.request.ident == 'hello_return_error'
31
+ context.response.add_error(
32
+ 'generic.invalid_string',
33
+ :message => 'Returning error as requested',
34
+ :reference => { :another => 'no other ident', :field_name => 'no ident' }
35
+ )
36
+ else
37
+ context.response.set_resource( { 'inner' => 'shown' } )
38
+ end
39
+ end
40
+
41
+ def create( context )
42
+ expectable_hook( context )
43
+
44
+ # If called with deja-vu requested, then to test that inter-resource
45
+ # local calls deal with 422-style responses properly return a simulation
46
+ # of an actual duplication. The error we add here should _not_ lead to an
47
+ # error to the caller as Hoodoo should turn it into a 422.
48
+ #
49
+ if ( context.request.deja_vu )
50
+ context.response.add_error( 'generic.invalid_duplication', :reference => { :field_name => 'deja' } )
51
+ end
52
+
53
+ # This tests an invalid response type from a service during a local
54
+ # inter-resource call.
55
+ #
56
+ if context.request.body[ 'foo' ] == 'broken_response'
57
+ context.response.body = self
58
+ else
59
+ context.response.body = { 'inner' => 'created' }
60
+ end
61
+ end
62
+
63
+ def update( context )
64
+ expectable_hook( context )
65
+ context.response.add_header( 'X-Example-Header', 'example' )
66
+ context.response.body = { 'inner' => 'updated' }
67
+ end
68
+
69
+ def delete( context )
70
+ expectable_hook( context )
71
+ context.response.body = { 'inner' => 'deleted' }
72
+ end
73
+
74
+ # ...So we can expect any instance of this class to receive this message and
75
+ # check on the data it was given.
76
+ #
77
+ def expectable_hook( context )
78
+ end
79
+ end
80
+
81
+ class RSpecTestInterResourceCallsAInterface < Hoodoo::Services::Interface
82
+ interface :RSpecTestInterResourceCallsAResource do
83
+ endpoint :rspec_test_inter_resource_calls_a, RSpecTestInterResourceCallsAImplementation
84
+ public_actions :delete
85
+
86
+ embeds :foo, :bar
87
+
88
+ to_list do
89
+ search :offset, :limit
90
+ sort :extra => [ :up, :down ]
91
+ end
92
+
93
+ # Most of the to-create/to-update options are tested already; just use this
94
+ # small bit of interface definition to make sure that inter-resource local
95
+ # calls get the validation performed correctly on the "inner" service.
96
+
97
+ to_create do
98
+ text :foo, :required => true
99
+ end
100
+
101
+ errors_for 'service_calls_a' do
102
+ error 'triggered', :status => 412, 'message' => 'Error Triggered'
103
+ end
104
+ end
105
+ end
106
+
107
+ # This calls ...AImplementation. Its interface contains two public actions,
108
+ # one calling through to a public action in ...AInterface, checking that a
109
+ # public action calling to another public action is successful; the other
110
+ # calls a secure method in ...AInterface and is used to make sure that a
111
+ # public action calling to a secure action is handled correctly (i.e. overall
112
+ # response is 401).
113
+ #
114
+ # Note intentional mix of 'body= ', 'set_resource' and 'set_resources' just
115
+ # to make sure those all get some coverage again.
116
+ #
117
+ class RSpecTestInterResourceCallsBImplementation < Hoodoo::Services::Implementation
118
+ def list( context )
119
+ expectable_hook( context )
120
+
121
+ # Call RSpecTestInterResourceCallsAImplementation#list, with a query string
122
+ # that 'searches' for offset and limit quantities that we get from the
123
+ # inbound request. The sort parameter tests non-Array sorting, with the
124
+ # direction parameter testing Array-based sorting.
125
+
126
+ qd = {
127
+ :sort => 'extra',
128
+ :direction => [ 'down' ],
129
+ :search => {
130
+ :offset => context.request.list.offset,
131
+ :limit => context.request.list.limit
132
+ },
133
+ :_embed => [ 'foo' ]
134
+ }
135
+
136
+ # Set limit to 10 to force an invalid search parameter which should cause
137
+ # a 422 in A, which B merges and returns.
138
+
139
+ if (context.request.list.limit.to_s == '10')
140
+ qd[:search][:foo] = 'bar'
141
+ end
142
+
143
+ result = context.resource( :RSpecTestInterResourceCallsAResource ).list(qd)
144
+ result.adds_errors_to?( context.response.errors )
145
+ expectable_result_hook( result )
146
+ context.response.set_resources( [ { :result => result, :options => result.response_options } ] )
147
+ end
148
+
149
+ def show( context )
150
+ expectable_hook( context )
151
+
152
+ if context.request.ident == 'call_c'
153
+ result = context.resource( :RSpecTestInterResourceCallsCResource ).show(
154
+ context.request.ident,
155
+ {}
156
+ )
157
+ else
158
+ result = context.resource( :RSpecTestInterResourceCallsAResource ).show(
159
+ 'hello' + context.request.ident,
160
+ { :_embed => :foo }
161
+ )
162
+ end
163
+
164
+ expectable_result_hook( result )
165
+
166
+ context.response.add_errors( result.platform_errors )
167
+ context.response.body = { :result => result, :options => result.response_options }
168
+ end
169
+
170
+ def create( context )
171
+ expectable_hook( context )
172
+
173
+ endpoint = context.resource( :RSpecTestInterResourceCallsAResource )
174
+
175
+ # This implementation makes an inter-resource call which just passes
176
+ # on the caller's provided body data. If someone used X-Resource-UUID
177
+ # in their top-level call with permission, 'body' will contain 'id'
178
+ # and that'll be rejected if we pass it through an inter-resource call
179
+ # (you must use the high-level interface to do that), assuming things
180
+ # are working properly andthe X-Resource-UUID specification is *not*
181
+ # automatically inherited to inter-resource endpoints.
182
+ #
183
+ # This is for *top level* calls specifying UUIDs to *this resource*.
184
+ #
185
+ context.request.body.delete( 'id' )
186
+
187
+ if context.request.body[ 'foo' ] == 'specify_uuid'
188
+
189
+ # If given the magic string "specify_uuid" in the mandatory resource
190
+ # text field "foo", then specifically make an inter-resource call with
191
+ # a resource UUID specified for the inner resource. This checks the
192
+ # permissions handling on "internal" inter-resource calls.
193
+ #
194
+ # This is for *inter-resource* calls *from* this resource to the target
195
+ # resource and has nothing to do with anything the top-level API caller
196
+ # specified.
197
+ #
198
+ endpoint.resource_uuid = Hoodoo::UUID.generate()
199
+
200
+ elsif context.request.body[ 'foo' ] == 'deja_vu'
201
+
202
+ # This tests an inter-resource call specifying deja-vu and dealing with
203
+ # responses.
204
+ #
205
+ endpoint.deja_vu = true
206
+
207
+ end
208
+
209
+ result = endpoint.create(
210
+ context.request.body,
211
+ { :_embed => 'foo' }
212
+ )
213
+
214
+ expectable_result_hook( result )
215
+
216
+ # There are two tests hidden here. Note that if there's an error,
217
+ # we're actually not setting response body, leaving it 'nil'; make
218
+ # sure nothing barfs on that.
219
+ #
220
+ if result.adds_errors_to?( context.response.errors )
221
+ expectable_result_failure_hook( result )
222
+ else
223
+ if result.empty?
224
+ context.response.set_resource( { :result => 'I experienced deja-vu',
225
+ :options => result.response_options } )
226
+ else
227
+ context.response.set_resource( { :result => result,
228
+ :options => result.response_options,
229
+ :dated_from => context.request.dated_from } )
230
+ end
231
+ end
232
+ end
233
+
234
+ def update( context )
235
+ expectable_hook( context )
236
+ result = context.resource( :RSpecTestInterResourceCallsAResource ).update(
237
+ 'hello' + context.request.ident,
238
+ context.request.body,
239
+ { :_embed => 'foo' }
240
+ )
241
+ expectable_result_hook( result )
242
+ context.response.add_errors( result.platform_errors )
243
+ context.response.body = { :result => result, :options => result.response_options }
244
+ end
245
+
246
+ def delete( context )
247
+ return context.response.not_found( context.request.ident ) if ( context.request.ident == 'simulate_404' )
248
+
249
+ expectable_hook( context )
250
+ result = context.resource( :RSpecTestInterResourceCallsAResource ).delete(
251
+ 'hello' + context.request.ident,
252
+ { :_embed => [ :foo ] }
253
+ )
254
+ expectable_result_hook( result )
255
+ context.response.add_errors( result.platform_errors )
256
+ context.response.body = { :result => result, :options => result.response_options }
257
+ end
258
+
259
+ # ...So we can expect any instance of this class to receive these messages
260
+ # and check on the data it was given or is returning.
261
+ #
262
+ def expectable_hook( context )
263
+ end
264
+ def expectable_result_hook( result )
265
+ end
266
+ def expectable_result_failure_hook( result )
267
+ end
268
+ end
269
+
270
+ class RSpecTestInterResourceCallsBInterface < Hoodoo::Services::Interface
271
+ interface :RSpecTestInterResourceCallsBResource do
272
+ endpoint :rspec_test_inter_resource_calls_b, RSpecTestInterResourceCallsBImplementation
273
+ public_actions :update, :delete
274
+ end
275
+ end
276
+
277
+ class RSpecTestInterResourceCallsCImplementation < Hoodoo::Services::Implementation
278
+
279
+ # This gets inter-resource called from ...BImplementation too. It only implements
280
+ # one action so is used for action validation tests.
281
+
282
+ def list( context )
283
+ context.response.body = [ 1,2,3,4 ]
284
+ end
285
+ end
286
+
287
+ class RSpecTestInterResourceCallsCInterface < Hoodoo::Services::Interface
288
+ interface :RSpecTestInterResourceCallsCResource do
289
+ endpoint :rspec_test_inter_resource_calls_c, RSpecTestInterResourceCallsCImplementation
290
+ actions :list
291
+ end
292
+ end
293
+
294
+ class RSpecTestInterResourceCalls < Hoodoo::Services::Service
295
+ comprised_of RSpecTestInterResourceCallsAInterface,
296
+ RSpecTestInterResourceCallsBInterface,
297
+ RSpecTestInterResourceCallsCInterface
298
+ end
299
+
300
+ describe Hoodoo::Services::Middleware::InterResourceLocal do
301
+
302
+ before :each do
303
+ @test_uuid = Hoodoo::UUID.generate()
304
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
305
+ @test_session = @old_test_session.dup
306
+ permissions = Hoodoo::Services::Permissions.new # (this is "default-else-deny")
307
+ permissions.set_default_fallback( Hoodoo::Services::Permissions::ALLOW )
308
+ @test_session.permissions = permissions
309
+ @test_session.scoping.authorised_http_headers = [] # (no secured headers allowed to start with)
310
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
311
+ end
312
+
313
+ after :each do
314
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
315
+ end
316
+
317
+ # Middleware maintains class-level record of whether or not any interfaces
318
+ # had public actions for efficiency; ensure this is cleared after all these
319
+ # tests run, so it's a clean slate for the next set.
320
+ #
321
+ after :all do
322
+ Hoodoo::Services::Middleware::class_variable_set( '@@interfaces_have_public_methods', false )
323
+ end
324
+
325
+ def app
326
+ Rack::Builder.new do
327
+ use Hoodoo::Services::Middleware
328
+ run RSpecTestInterResourceCalls.new
329
+ end
330
+ end
331
+
332
+ before :each do
333
+ @interaction_id = Hoodoo::UUID.generate()
334
+ end
335
+
336
+ before :example, :check_callbacks => true do
337
+ expect_any_instance_of( RSpecTestInterResourceCallsBImplementation ).to receive( :before ).once
338
+ # -> A
339
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:before ).once
340
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:after ).once
341
+ # <- B
342
+ expect_any_instance_of( RSpecTestInterResourceCallsBImplementation ).to receive( :after ).once
343
+ end
344
+
345
+ def headers_for( locale: nil,
346
+ dated_at: nil,
347
+ dated_from: nil,
348
+ deja_vu: nil,
349
+ resource_uuid: nil )
350
+
351
+ headers = {
352
+ 'HTTP_X_INTERACTION_ID' => @interaction_id,
353
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8'
354
+ }
355
+
356
+ headers[ 'HTTP_CONTENT_LANGUAGE' ] = locale unless locale.nil?
357
+ headers[ 'HTTP_ACCEPT_LANGUAGE' ] = locale unless locale.nil?
358
+ headers[ 'HTTP_X_RESOURCE_UUID' ] = resource_uuid unless resource_uuid.nil?
359
+ headers[ 'HTTP_X_DATED_AT' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_at ) unless dated_at.nil?
360
+ headers[ 'HTTP_X_DATED_FROM' ] = Hoodoo::Utilities.nanosecond_iso8601( dated_from ) unless dated_from.nil?
361
+ headers[ 'HTTP_X_DEJA_VU' ] = 'yes' if deja_vu == true
362
+
363
+ return headers
364
+ end
365
+
366
+ def expect_response_options_for( options )
367
+ expect( options ).to_not be_nil
368
+ expect( options[ 'service_response_time' ] ).to_not be_nil
369
+ expect( options[ 'interaction_id' ] ).to_not be_nil
370
+ expect( Hoodoo::UUID.valid?( options[ 'interaction_id' ] ) ).to eq( true )
371
+ end
372
+
373
+ def list_things(locale: nil, dated_at: nil)
374
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:list).once.and_call_original
375
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
376
+ expect(context.request.dated_at).to eq(dated_at) # Is used
377
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
378
+ expect(context.request.deja_vu).to eq(nil) # Not used => expect 'nil'
379
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
380
+ end
381
+ # -> A
382
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:list).once.and_call_original
383
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
384
+ expect(context.owning_interaction.interaction_id).to eq(@interaction_id)
385
+ expect(context.request.body).to be_nil
386
+ expect(context.request.embeds).to eq(['foo'])
387
+ expect(context.request.uri_path_components).to eq([])
388
+ expect(context.request.uri_path_extension).to eq('')
389
+ expect(context.request.list.offset).to eq(0)
390
+ expect(context.request.list.limit).to eq(50)
391
+ expect(context.request.list.sort_data).to eq({'extra'=>'down'})
392
+ expect(context.request.locale).to eq(locale || 'en-nz')
393
+ expect(context.request.dated_at).to eq(dated_at) # Is passed through inter-resource calls
394
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
395
+ expect(context.request.deja_vu).to eq(nil) # Not used => expect 'nil'
396
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
397
+ end
398
+ # <- B
399
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
400
+ expect(result).to eq([1,2,3,4])
401
+ expect(result.dataset_size).to eq(4321)
402
+ end
403
+
404
+ get '/v1/rspec_test_inter_resource_calls_b',
405
+ nil,
406
+ headers_for(locale: locale, dated_at: dated_at)
407
+
408
+ expect(last_response.status).to eq(200)
409
+
410
+ result = JSON.parse(last_response.body)
411
+
412
+ expect(result['_data']).to_not be_nil
413
+ expect(result['_data'][0]).to_not be_nil
414
+ expect(result['_data'][0]['result']).to eq([1,2,3,4])
415
+ expect_response_options_for(result['_data'][0]['options'])
416
+ end
417
+
418
+ it 'manages ActiveRecord when implementation is called in its presence' do
419
+ pool = ActiveRecord::Base.connection_pool
420
+
421
+ expect( ActiveRecord::Base ).to receive( :connection_pool ).twice.and_call_original
422
+ expect( pool ).to receive( :with_connection ).twice.and_call_original
423
+
424
+ list_things()
425
+ end
426
+
427
+ # X-Dated-From is tested for POST elsewhere and X-Dated-At for GET
428
+ # is tested here.
429
+ #
430
+ context 'when dated_at is an invalid datetime' do
431
+ datetimes = [
432
+ "2015-01-01T01:00:00+0100",
433
+ "2015-01-01T01:00:00-0100",
434
+ "2015-01T01:00:00+01:00",
435
+ "2015-001-01T01:00:00-32:00",
436
+ "2015-01T01:00:00-2900",
437
+ "2015-01-01",
438
+ "not-a-date"
439
+ ]
440
+
441
+ datetimes.each do |datetime|
442
+ it "complains about a bad X-Dated-At header of #{datetime}" do
443
+ headers = headers_for(locale: 'en-nz', dated_at: DateTime.now)
444
+ headers['HTTP_X_DATED_AT'] = datetime
445
+
446
+ get '/v1/rspec_test_inter_resource_calls_b',
447
+ nil,
448
+ headers
449
+
450
+ expect(last_response.status).to eq(422)
451
+
452
+ result = JSON.parse(last_response.body)
453
+
454
+ expect(result['errors'][0]['code']).to eq('generic.malformed')
455
+ expect(result['errors'][0]['message']).to eq("X-Dated-At header value '#{ datetime }' is invalid")
456
+ expect(result['errors'][0]['reference']).to eq("X-Dated-At")
457
+ end
458
+ end
459
+ end
460
+
461
+ it 'lists things with callbacks', :check_callbacks => true do
462
+ list_things()
463
+ end
464
+
465
+ it 'lists things without callbacks' do
466
+ list_things()
467
+ end
468
+
469
+ it 'lists things with a custom locale and dated_at time' do
470
+ list_things(locale: 'foo', dated_at: DateTime.now)
471
+ end
472
+
473
+ it 'should report middleware level errors from the secondary service' do
474
+ get '/v1/rspec_test_inter_resource_calls_b?limit=10', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
475
+
476
+ expect(last_response.status).to eq(422)
477
+
478
+ result = JSON.parse(last_response.body)
479
+
480
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
481
+ expect(result['errors'][0]['message']).to eq('One or more malformed or invalid query string parameters')
482
+ expect(result['errors'][0]['reference']).to eq('search: foo')
483
+ end
484
+
485
+ it 'should report custom errors from the secondary service' do
486
+ get '/v1/rspec_test_inter_resource_calls_b?offset=42', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
487
+
488
+ expect(last_response.status).to eq(412)
489
+
490
+ result = JSON.parse(last_response.body)
491
+
492
+ expect(result['errors'][0]['code']).to eq('service_calls_a.triggered')
493
+ expect(result['errors'][0]['reference']).to eq('42')
494
+ end
495
+
496
+ it 'handles incorrect return types from the "inner" service' do
497
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:list) do | instance, context |
498
+ context.response.body = {}
499
+ end
500
+
501
+ get '/v1/rspec_test_inter_resource_calls_b', nil, headers_for()
502
+
503
+ result = JSON.parse(last_response.body)
504
+
505
+ expect(result['errors'][0]['code']).to eq('platform.fault')
506
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Middleware: Unexpected response type 'Hash' received from a local inter-resource call for action 'list'")
507
+ end
508
+
509
+ it 'gets the correct type back when an inter-resource local call generates an error' do
510
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:list) do | instance, context |
511
+ context.response.add_error( 'platform.malformed' )
512
+ end
513
+
514
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook) do | instance, result |
515
+ expect(result).to be_a(Hoodoo::Client::AugmentedArray)
516
+ expect(result.platform_errors.has_errors?).to eq(true)
517
+ end
518
+
519
+ get '/v1/rspec_test_inter_resource_calls_b', nil, headers_for()
520
+
521
+ result = JSON.parse(last_response.body)
522
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
523
+ end
524
+
525
+ def show_things(locale: nil, dated_at: nil)
526
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:show).once.and_call_original
527
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
528
+ expect(context.request.dated_at).to eq(dated_at) # Is used
529
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
530
+ expect(context.request.deja_vu).to eq(nil) # Not used => expect 'nil'
531
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
532
+ end
533
+ # -> A
534
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:show).once.and_call_original
535
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
536
+ expect(context.owning_interaction.interaction_id).to eq(@interaction_id)
537
+ expect(context.request.body).to be_nil
538
+ expect(context.request.embeds).to eq(['foo'])
539
+ expect(context.request.uri_path_components).to eq(['helloworld'])
540
+ expect(context.request.ident).to eq('helloworld')
541
+ expect(context.request.uri_path_extension).to eq('')
542
+ expect(context.request.list.offset).to eq(0)
543
+ expect(context.request.list.limit).to eq(50)
544
+ expect(context.request.locale).to eq(locale || 'en-nz')
545
+ expect(context.request.dated_at).to eq(dated_at) # Is passed through inter-resource calls
546
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
547
+ expect(context.request.deja_vu).to eq(nil) # Not used => expect 'nil'
548
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
549
+ end
550
+ # <- B
551
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
552
+ expect(result).to eq({ 'inner' => 'shown' })
553
+ end
554
+
555
+ get '/v1/rspec_test_inter_resource_calls_b/world',
556
+ nil,
557
+ headers_for(locale: locale, dated_at: dated_at)
558
+
559
+ expect(last_response.status).to eq(200)
560
+
561
+ result = JSON.parse(last_response.body)
562
+ expect_response_options_for(result['options'])
563
+
564
+ expect(result['result']).to eq({'inner' => 'shown'})
565
+ end
566
+
567
+ it 'shows things with callbacks', :check_callbacks => true do
568
+ show_things()
569
+ end
570
+
571
+ it 'shows things without callbacks' do
572
+ show_things()
573
+ end
574
+
575
+ it 'shows things with a custom locale and dated_at time' do
576
+ show_things(locale: 'bar', dated_at: DateTime.now)
577
+ end
578
+
579
+ it 'handles incorrect return types from the "inner" service' do
580
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:show) do | instance, context |
581
+ context.response.body = []
582
+ end
583
+
584
+ get '/v1/rspec_test_inter_resource_calls_b/world', nil, headers_for()
585
+
586
+ result = JSON.parse(last_response.body)
587
+
588
+ expect(result['errors'][0]['code']).to eq('platform.fault')
589
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Middleware: Unexpected response type 'Array' received from a local inter-resource call for action 'show'")
590
+ end
591
+
592
+ it 'gets the correct type back when an inter-resource local call generates an error' do
593
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:show) do | instance, context |
594
+ context.response.add_error( 'platform.malformed' )
595
+ end
596
+
597
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook) do | instance, result |
598
+ expect(result).to be_a(Hoodoo::Client::AugmentedHash)
599
+ expect(result.platform_errors.has_errors?).to eq(true)
600
+ end
601
+
602
+ get '/v1/rspec_test_inter_resource_calls_b/world', nil, headers_for()
603
+
604
+ result = JSON.parse(last_response.body)
605
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
606
+ end
607
+
608
+ def create_things(locale: nil, dated_from: nil, deja_vu: nil, resource_uuid: nil)
609
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:create).once.and_call_original
610
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
611
+ expect(context.request.dated_at).to eq(nil) # Not used => expect 'nil'
612
+ expect(context.request.dated_from).to eq(dated_from) # Is used
613
+ expect(context.request.deja_vu).to eq(deja_vu) # Is used
614
+ expect(context.request.resource_uuid).to eq(resource_uuid) # Is used
615
+ end
616
+ # -> A
617
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:create).once.and_call_original
618
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
619
+ expect(context.owning_interaction.interaction_id).to eq(@interaction_id)
620
+ expect(context.request.body).to eq({'foo' => 'required'})
621
+ expect(context.request.embeds).to eq(['foo'])
622
+ expect(context.request.uri_path_components).to eq([])
623
+ expect(context.request.ident).to be_nil
624
+ expect(context.request.locale).to eq(locale || 'en-nz')
625
+ expect(context.request.dated_at).to eq(nil) # Not used => expect 'nil'
626
+ expect(context.request.dated_from).to eq(dated_from) # Is passed through inter-resource calls
627
+ expect(context.request.deja_vu).to eq(nil) # Is not passed through inter-resource calls
628
+ expect(context.request.resource_uuid).to eq(nil) # Is not passed through inter-resource calls
629
+ end
630
+ # <- B
631
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
632
+ expect(result).to eq({ 'inner' => 'created' })
633
+ end
634
+
635
+ post '/v1/rspec_test_inter_resource_calls_b/',
636
+ '{"foo": "required"}',
637
+ headers_for(locale: locale, dated_from: dated_from, deja_vu: deja_vu, resource_uuid: resource_uuid)
638
+
639
+ expect(last_response.status).to eq(200)
640
+
641
+ result = JSON.parse(last_response.body)
642
+ expect_response_options_for(result['options'])
643
+
644
+ expect(result['result']).to eq({'inner' => 'created'})
645
+ expect(result).to have_key('dated_from')
646
+ expect(result['dated_from']).to eq(dated_from.nil? ? nil : dated_from.to_s)
647
+ end
648
+
649
+ def fail_to_create_things
650
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:create).once.and_call_original
651
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once
652
+ # -> A
653
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to_not receive(:create)
654
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to_not receive(:expectable_hook)
655
+ # <- B
656
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook)
657
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_failure_hook)
658
+
659
+ post '/v1/rspec_test_inter_resource_calls_b/', '{"sum": 7}', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
660
+
661
+ expect(last_response.status).to eq(422)
662
+
663
+ result = JSON.parse(last_response.body)
664
+
665
+ expect(result['errors'].size).to eq(1)
666
+ expect(result['errors'][0]['message']).to eq('Field `foo` is required')
667
+ end
668
+
669
+ # X-Dated-At is tested for GET elsewhere and X-Dated-From for POST
670
+ # is tested here.
671
+ #
672
+ context 'when dated_from is an invalid datetime' do
673
+ datetimes = [
674
+ "2015-01-01T01:00:00+0100",
675
+ "2015-01-01T01:00:00-0100",
676
+ "2015-01T01:00:00+01:00",
677
+ "2015-001-01T01:00:00-32:00",
678
+ "2015-01T01:00:00-2900",
679
+ "2015-01-01",
680
+ "not-a-date"
681
+ ]
682
+
683
+ datetimes.each do |datetime|
684
+ it "complains about a bad X-Dated-At header of #{datetime}" do
685
+ headers = headers_for(locale: 'en-nz', dated_at: DateTime.now)
686
+ headers['HTTP_X_DATED_FROM'] = datetime
687
+
688
+ post '/v1/rspec_test_inter_resource_calls_b',
689
+ '{"foo": "required"}',
690
+ headers
691
+
692
+ expect(last_response.status).to eq(422)
693
+
694
+ result = JSON.parse(last_response.body)
695
+
696
+ expect(result['errors'][0]['code']).to eq('generic.malformed')
697
+ expect(result['errors'][0]['message']).to eq("X-Dated-From header value '#{ datetime }' is invalid")
698
+ expect(result['errors'][0]['reference']).to eq("X-Dated-From")
699
+ end
700
+ end
701
+ end
702
+
703
+ it 'creates things with callbacks', :check_callbacks => true do
704
+ create_things()
705
+ end
706
+
707
+ it 'creates things without callbacks' do
708
+ create_things()
709
+ end
710
+
711
+ it 'creates things with a custom locale and passes through dated_from' do
712
+ create_things(locale: 'baz', dated_from: DateTime.now)
713
+ end
714
+
715
+ it 'creates things and does not pass through deja_vu' do
716
+ create_things(deja_vu: true)
717
+ end
718
+
719
+ it 'refuses to create things when the inner service gets invalid data, with callbacks' do
720
+ expect_any_instance_of( RSpecTestInterResourceCallsBImplementation ).to receive( :before ).once
721
+ # -> A
722
+ expect_any_instance_of( RSpecTestInterResourceCallsAImplementation ).to_not receive( :before )
723
+ expect_any_instance_of( RSpecTestInterResourceCallsAImplementation ).to_not receive( :after )
724
+ # <- B
725
+ expect_any_instance_of( RSpecTestInterResourceCallsBImplementation ).to receive( :after ).once
726
+
727
+ fail_to_create_things()
728
+ end
729
+
730
+ it 'refuses to create things when the inner service gets invalid data, without callbacks' do
731
+ fail_to_create_things()
732
+ end
733
+
734
+ it 'creates things with a custom UUID given permission, but does not pass it through' do
735
+ @test_session.scoping.authorised_http_headers = [ 'HTTP_X_RESOURCE_UUID' ]
736
+ create_things(resource_uuid: Hoodoo::UUID.generate())
737
+ end
738
+
739
+ it 'fails to create things with a custom UUID if not given permission' do
740
+ post '/v1/rspec_test_inter_resource_calls_b/',
741
+ '{"foo": "required"}',
742
+ headers_for(resource_uuid: Hoodoo::UUID.generate())
743
+
744
+ expect(last_response.status).to eq(403)
745
+
746
+ result = JSON.parse(last_response.body)
747
+
748
+ expect(result['errors'].size).to eq(1)
749
+ expect(result['errors'][0]['code']).to eq('platform.forbidden')
750
+ expect(result['errors'][0]['reference']).to be_nil # Ensure no information disclosure vulnerability
751
+ end
752
+
753
+ it 'can specify a UUID via an inter-resource call if it has top-level permission' do
754
+ @test_session.scoping.authorised_http_headers = [ 'HTTP_X_RESOURCE_UUID' ]
755
+
756
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
757
+ expect(context.request.resource_uuid).to eq(nil) # Is not specified by top-level caller
758
+ end
759
+ # -> A
760
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
761
+ expect(context.request.resource_uuid).to_not be_nil
762
+ expect(Hoodoo::UUID.valid?(context.request.resource_uuid)).to eq(true)
763
+ end
764
+ # <- B
765
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
766
+ expect(result).to eq({ 'inner' => 'created' })
767
+ end
768
+
769
+ post '/v1/rspec_test_inter_resource_calls_b/',
770
+ JSON.fast_generate({:foo => 'specify_uuid'}),
771
+ headers_for()
772
+
773
+ expect(last_response.status).to eq(200)
774
+
775
+ result = JSON.parse(last_response.body)
776
+ expect_response_options_for(result['options'])
777
+
778
+ expect(result['result']).to eq({'inner' => 'created'})
779
+ expect(result).to have_key('dated_from')
780
+ expect(result['dated_from']).to be_nil
781
+ end
782
+
783
+ it 'cannot specify a UUID via an inter-resource call if it does not have top-level permission' do
784
+ post '/v1/rspec_test_inter_resource_calls_b/',
785
+ JSON.fast_generate({:foo => 'specify_uuid'}),
786
+ headers_for()
787
+
788
+ expect(last_response.status).to eq(403)
789
+
790
+ result = JSON.parse(last_response.body)
791
+
792
+ expect(result['errors'].size).to eq(1)
793
+ expect(result['errors'][0]['code']).to eq('platform.forbidden')
794
+ expect(result['errors'][0]['reference']).to be_nil # Ensure no information disclosure vulnerability
795
+ end
796
+
797
+ it 'creates things with the inter-resource local call asking for deja-vu and being told there are duplicates' do
798
+ post '/v1/rspec_test_inter_resource_calls_b/',
799
+ JSON.fast_generate({:foo => 'deja_vu'}),
800
+ headers_for()
801
+
802
+ expect(last_response.status).to eq(200)
803
+
804
+ result = JSON.parse(last_response.body)
805
+ expect_response_options_for(result['options'])
806
+
807
+ expect(result['result']).to eq('I experienced deja-vu')
808
+ end
809
+
810
+ it 'handles broken service responses' do
811
+ post '/v1/rspec_test_inter_resource_calls_b/',
812
+ JSON.fast_generate({:foo => 'broken_response'}),
813
+ headers_for()
814
+
815
+ expect(last_response.status).to eq(500)
816
+ result = JSON.parse(last_response.body)
817
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Middleware: Unexpected response type 'RSpecTestInterResourceCallsAImplementation' received from a local inter-resource call for action 'create'")
818
+ end
819
+
820
+ it 'handles incorrect return types from the "inner" service' do
821
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:create) do | instance, context |
822
+ context.response.body = OpenStruct.new
823
+ end
824
+
825
+ post '/v1/rspec_test_inter_resource_calls_b/',
826
+ JSON.fast_generate({:foo => 'bar'}),
827
+ headers_for()
828
+
829
+ result = JSON.parse(last_response.body)
830
+
831
+ expect(result['errors'][0]['code']).to eq('platform.fault')
832
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Middleware: Unexpected response type 'OpenStruct' received from a local inter-resource call for action 'create'")
833
+ end
834
+
835
+ it 'gets the correct type back when an inter-resource local call generates an error' do
836
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:create) do | instance, context |
837
+ context.response.add_error( 'platform.malformed' )
838
+ end
839
+
840
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook) do | instance, result |
841
+ expect(result).to be_a(Hoodoo::Client::AugmentedHash)
842
+ expect(result.platform_errors.has_errors?).to eq(true)
843
+ end
844
+
845
+ post '/v1/rspec_test_inter_resource_calls_b/',
846
+ JSON.fast_generate({:foo => 'bar'}),
847
+ headers_for()
848
+
849
+ result = JSON.parse(last_response.body)
850
+
851
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
852
+ end
853
+
854
+ def update_things(locale: nil)
855
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:update).once.and_call_original
856
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
857
+ expect(context.request.dated_at).to eq(nil) # Not used => expect 'nil'
858
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
859
+ expect(context.request.deja_vu).to eq(nil) # Not used => expect 'nil'
860
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
861
+ end
862
+ # -> A
863
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:update).once.and_call_original
864
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
865
+ expect(context.owning_interaction.interaction_id).to eq(@interaction_id)
866
+ expect(context.request.body).to eq({'sum' => 70})
867
+ expect(context.request.embeds).to eq(['foo'])
868
+ expect(context.request.uri_path_components).to eq(['helloworld'])
869
+ expect(context.request.ident).to eq('helloworld')
870
+ expect(context.request.locale).to eq(locale || 'en-nz')
871
+ expect(context.request.dated_at).to eq(nil) # Not used => expect 'nil'
872
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
873
+ expect(context.request.deja_vu).to eq(nil) # Not used => expect 'nil'
874
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
875
+ end
876
+ # <- B
877
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
878
+ expect(result).to eq({ 'inner' => 'updated' })
879
+ end
880
+
881
+ patch '/v1/rspec_test_inter_resource_calls_b/world',
882
+ '{"sum": 70}',
883
+ headers_for(locale: locale)
884
+
885
+ expect(last_response.status).to eq(200)
886
+
887
+ result = JSON.parse(last_response.body)
888
+ expect_response_options_for(result['options'])
889
+ expect(result['options']['example_header']).to eq('example')
890
+
891
+ expect(result['result']).to eq({'inner' => 'updated'})
892
+ end
893
+
894
+ it 'updates things with callbacks', :check_callbacks => true do
895
+ update_things()
896
+ end
897
+
898
+ it 'updates things with a custom locale' do
899
+ update_things(locale: 'boo')
900
+ end
901
+
902
+ it 'handles incorrect return types from the "inner" service' do
903
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:update) do | instance, context |
904
+ context.response.body = "string"
905
+ end
906
+
907
+ patch '/v1/rspec_test_inter_resource_calls_b/world', '{}', headers_for()
908
+
909
+ result = JSON.parse(last_response.body)
910
+
911
+ expect(result['errors'][0]['code']).to eq('platform.fault')
912
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Middleware: Unexpected response type 'String' received from a local inter-resource call for action 'update'")
913
+ end
914
+
915
+ it 'gets the correct type back when an inter-resource local call generates an error' do
916
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:update) do | instance, context |
917
+ context.response.add_error( 'platform.malformed' )
918
+ end
919
+
920
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook) do | instance, result |
921
+ expect(result).to be_a(Hoodoo::Client::AugmentedHash)
922
+ expect(result.platform_errors.has_errors?).to eq(true)
923
+ end
924
+
925
+ patch '/v1/rspec_test_inter_resource_calls_b/world', '{}', headers_for()
926
+
927
+ result = JSON.parse(last_response.body)
928
+
929
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
930
+ end
931
+
932
+ def delete_things(locale: nil, deja_vu: nil)
933
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:delete).once.and_call_original
934
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
935
+ expect(context.request.dated_at).to eq(nil) # Not used => expect 'nil'
936
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
937
+ expect(context.request.deja_vu).to eq(deja_vu) # Is used
938
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
939
+ end
940
+ # -> A
941
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:delete).once.and_call_original
942
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
943
+ expect(context.owning_interaction.interaction_id).to eq(@interaction_id)
944
+ expect(context.request.body).to be_nil
945
+ expect(context.request.embeds).to eq(['foo'])
946
+ expect(context.request.uri_path_components).to eq(['helloworld'])
947
+ expect(context.request.ident).to eq('helloworld')
948
+ expect(context.request.locale).to eq(locale || 'en-nz')
949
+ expect(context.request.dated_at).to eq(nil) # Not used => expect 'nil'
950
+ expect(context.request.dated_from).to eq(nil) # Not used => expect 'nil'
951
+ expect(context.request.deja_vu).to eq(nil) # Is not passed through inter-resource calls
952
+ expect(context.request.resource_uuid).to eq(nil) # Not used => expect 'nil'
953
+ end
954
+ # <- B
955
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once do | ignored_rspec_mock_instance, result |
956
+ expect(result).to eq({ 'inner' => 'deleted' })
957
+ end
958
+
959
+ delete '/v1/rspec_test_inter_resource_calls_b/world',
960
+ nil,
961
+ headers_for(locale: locale, deja_vu: deja_vu)
962
+
963
+ expect(last_response.status).to eq(200)
964
+
965
+ result = JSON.parse(last_response.body)
966
+ expect_response_options_for(result['options'])
967
+
968
+ expect(result['result']).to eq({'inner' => 'deleted'})
969
+ end
970
+
971
+ it 'deletes things with callbacks', :check_callbacks => true do
972
+ delete_things()
973
+ end
974
+
975
+ it 'deletes things without callbacks' do
976
+ delete_things()
977
+ end
978
+
979
+ it 'deletes things with a custom locale' do
980
+ delete_things(locale: 'bye')
981
+ end
982
+
983
+ it 'deletes things and passes through deja_vu' do
984
+ delete_things(deja_vu: true)
985
+ end
986
+
987
+ it 'deletes things with a simulated 404' do
988
+ delete '/v1/rspec_test_inter_resource_calls_b/simulate_404',
989
+ nil,
990
+ headers_for()
991
+
992
+ expect(last_response.status).to eq(404)
993
+ result = JSON.parse(last_response.body)
994
+ expect(result['errors'][0]['code']).to eq('generic.not_found')
995
+ end
996
+
997
+ it 'deletes things with a 204 with deja vu' do
998
+ delete '/v1/rspec_test_inter_resource_calls_b/simulate_404',
999
+ nil,
1000
+ headers_for(deja_vu: true)
1001
+
1002
+ expect(last_response.status).to eq(204)
1003
+ expect(last_response.body).to be_empty
1004
+ expect(last_response['X-Deja-Vu']).to eq('confirmed')
1005
+ end
1006
+
1007
+ it 'handles incorrect return types from the "inner" service' do
1008
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:delete) do | instance, context |
1009
+ context.response.body = instance
1010
+ end
1011
+
1012
+ delete '/v1/rspec_test_inter_resource_calls_b/world', nil, headers_for()
1013
+
1014
+ result = JSON.parse(last_response.body)
1015
+
1016
+ expect(result['errors'][0]['code']).to eq('platform.fault')
1017
+ expect(result['errors'][0]['message']).to eq("Hoodoo::Services::Middleware: Unexpected response type 'RSpecTestInterResourceCallsAImplementation' received from a local inter-resource call for action 'delete'")
1018
+ end
1019
+
1020
+ it 'gets the correct type back when an inter-resource local call generates an error' do
1021
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:delete) do | instance, context |
1022
+ context.response.add_error( 'platform.malformed' )
1023
+ end
1024
+
1025
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook) do | instance, result |
1026
+ expect(result).to be_a(Hoodoo::Client::AugmentedHash)
1027
+ expect(result.platform_errors.has_errors?).to eq(true)
1028
+ end
1029
+
1030
+ delete '/v1/rspec_test_inter_resource_calls_b/world', nil, headers_for()
1031
+
1032
+ result = JSON.parse(last_response.body)
1033
+ expect(result['errors'][0]['code']).to eq('platform.malformed')
1034
+ end
1035
+
1036
+ it 'should see errors from the inner call correctly' do
1037
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:show).once.and_call_original
1038
+ # -> A
1039
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:show).once.and_call_original
1040
+ expect_any_instance_of(RSpecTestInterResourceCallsAImplementation).to receive(:expectable_hook).once do | ignored_rspec_mock_instance, context |
1041
+ expect(context.request.body).to be_nil
1042
+ expect(context.request.embeds).to eq(['foo'])
1043
+ expect(context.request.uri_path_components).to eq(['hello_return_error'])
1044
+ expect(context.request.ident).to eq('hello_return_error')
1045
+ expect(context.request.uri_path_extension).to eq('')
1046
+ expect(context.request.list.offset).to eq(0)
1047
+ expect(context.request.list.limit).to eq(50)
1048
+ end
1049
+ # <- B
1050
+ expect_any_instance_of(RSpecTestInterResourceCallsBImplementation).to receive(:expectable_result_hook).once.and_call_original
1051
+
1052
+ get '/v1/rspec_test_inter_resource_calls_b/_return_error', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1053
+ expect(last_response.status).to eq(422)
1054
+ result = JSON.parse(last_response.body)
1055
+ expect( result[ 'errors' ] ).to_not be_nil
1056
+ expect( result[ 'errors' ][ 0 ] ).to eq({
1057
+ 'code' => 'generic.invalid_string',
1058
+ 'message' => 'Returning error as requested',
1059
+ 'reference' => 'no ident,no other ident'
1060
+ })
1061
+ end
1062
+
1063
+ it 'should get told if an action is not supported' do
1064
+ expect_any_instance_of(RSpecTestInterResourceCallsCImplementation).to_not receive(:show)
1065
+ expect_any_instance_of(RSpecTestInterResourceCallsCImplementation).to_not receive(:expectable_hook)
1066
+
1067
+ get '/v1/rspec_test_inter_resource_calls_b/call_c', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1068
+ expect(last_response.status).to eq(405)
1069
+ result = JSON.parse(last_response.body)
1070
+ expect( result[ 'errors' ] ).to_not be_nil
1071
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.method_not_allowed' )
1072
+ end
1073
+
1074
+ context 'with no session' do
1075
+ before :example do
1076
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
1077
+ Hoodoo::Services::Middleware.set_test_session( nil )
1078
+ end
1079
+
1080
+ after :example do
1081
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
1082
+ end
1083
+
1084
+ it 'can call public-to-public actions successfully' do
1085
+ delete '/v1/rspec_test_inter_resource_calls_b/world', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1086
+ expect(last_response.status).to eq(200)
1087
+ end
1088
+
1089
+ it 'cannot call the secure update method in the other service without a session' do
1090
+ patch '/v1/rspec_test_inter_resource_calls_b/world', '{"sum": 70}', { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
1091
+ expect(last_response.status).to eq(401)
1092
+ end
1093
+ end
1094
+ end