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,186 @@
1
+ require 'spec_helper'
2
+
3
+ # Most coverage for 'dated-at' is done in passing through all the various
4
+ # integration, inter-resource local and inter-resource remote tests in other
5
+ # files. This particular test file was originally created to check the
6
+ # following scenario:
7
+ #
8
+ # * Top-level incoming call hits #create in resource A
9
+ # * The payload indicates some sort of backdating is in use
10
+ # * Resource A calls resource B with a dated-at specifier
11
+ # * Resource B calls resource C without an explicit specifier
12
+ # * Resource C should inherit the dated-at context from B.
13
+ #
14
+ # Since the corresponding "dated-from" feature is *not* automaticalled passed
15
+ # through, there's no additional coverage for that here.
16
+
17
+ ###############################################################################
18
+
19
+ # A#create calls B#show calls C#show.
20
+
21
+ class RSpecDatedAtAImplementation < Hoodoo::Services::Implementation
22
+ def create( context )
23
+ created_at = Time.now
24
+ backdated_to = context.request.body[ 'backdated_to' ]
25
+ dated_at = Hoodoo::Utilities.rationalise_datetime( backdated_to || created_at )
26
+
27
+ if context.request.body[ 'use_resource_options' ]
28
+ endpoint = context.resource( :RSpecDatedAtB, 1, { :dated_at => dated_at } )
29
+ else
30
+ endpoint = context.resource( :RSpecDatedAtB )
31
+ endpoint.dated_at = dated_at
32
+ end
33
+
34
+ result = endpoint.show( context.request.body[ 'thing_to_show_in_b' ] )
35
+ context.response.set_resource( result ) unless result.platform_errors.has_errors?
36
+ end
37
+ end
38
+
39
+ class RSpecDatedAtBImplementation < Hoodoo::Services::Implementation
40
+ def show( context )
41
+ endpoint = context.resource( :RSpecDatedAtC )
42
+ result = endpoint.show( context.request.ident + '_to_c' )
43
+ context.response.set_resource( result ) unless result.platform_errors.has_errors?
44
+ end
45
+ end
46
+
47
+ class RSpecDatedAtCImplementation < Hoodoo::Services::Implementation
48
+ def show( context )
49
+ context.response.set_resource( {
50
+ 'id' => context.request.ident,
51
+ 'dated_at' => context.request.dated_at.to_s
52
+ } )
53
+ end
54
+ end
55
+
56
+ class RSpecDatedAtAInterface < Hoodoo::Services::Interface
57
+ interface :RSpecDatedAtA do
58
+ endpoint :rspec_dated_at_a, RSpecDatedAtAImplementation
59
+ end
60
+ end
61
+
62
+ class RSpecDatedAtBInterface < Hoodoo::Services::Interface
63
+ interface :RSpecDatedAtB do
64
+ endpoint :rspec_dated_at_b, RSpecDatedAtBImplementation
65
+ end
66
+ end
67
+
68
+ class RSpecDatedAtCInterface < Hoodoo::Services::Interface
69
+ interface :RSpecDatedAtC do
70
+ endpoint :rspec_dated_at_c, RSpecDatedAtCImplementation
71
+ end
72
+ end
73
+
74
+ # Three services to stand up individually for a remote inter-resource
75
+ # call check, one service to stand up on its own for a local call check.
76
+
77
+ class RSpecDatedAtAService < Hoodoo::Services::Service
78
+ comprised_of RSpecDatedAtAInterface
79
+ end
80
+
81
+ class RSpecDatedAtBService < Hoodoo::Services::Service
82
+ comprised_of RSpecDatedAtBInterface
83
+ end
84
+
85
+ class RSpecDatedAtCService < Hoodoo::Services::Service
86
+ comprised_of RSpecDatedAtCInterface
87
+ end
88
+
89
+ class RSpecDatedAtAllService < Hoodoo::Services::Service
90
+ comprised_of RSpecDatedAtAInterface,
91
+ RSpecDatedAtBInterface,
92
+ RSpecDatedAtCInterface
93
+ end
94
+
95
+ ###############################################################################
96
+
97
+ describe Hoodoo::Services::Middleware do
98
+ context 'deep local calls' do
99
+ after :all do
100
+ Hoodoo::Services::Middleware.flush_services_for_test()
101
+ end
102
+
103
+ def app
104
+ Rack::Builder.new do
105
+ use Hoodoo::Services::Middleware
106
+ run RSpecDatedAtAllService.new
107
+ end
108
+ end
109
+
110
+ def run_test_with_resource_options_boolean_of( value )
111
+ now = DateTime.now
112
+
113
+ data = {
114
+ :backdated_to => Hoodoo::Utilities.nanosecond_iso8601( now ),
115
+ :use_resource_options => value,
116
+ :thing_to_show_in_b => 'hello'
117
+ }
118
+
119
+ post '/v1/rspec_dated_at_a',
120
+ data.to_json,
121
+ { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
122
+
123
+ expect( last_response.status ).to eq( 200 )
124
+ result = JSON.parse( last_response.body )
125
+
126
+ expect( result ).to eq( {
127
+ 'id' => 'hello_to_c',
128
+ 'dated_at' => now.to_s
129
+ } )
130
+ end
131
+
132
+ it 'passes dates through properly via context options' do
133
+ run_test_with_resource_options_boolean_of( true )
134
+ end
135
+
136
+ it 'passes dates through properly via endpoint options' do
137
+ run_test_with_resource_options_boolean_of( false )
138
+ end
139
+ end
140
+
141
+ context 'deep remote calls' do
142
+ after :all do
143
+ Hoodoo::Services::Middleware.flush_services_for_test()
144
+ end
145
+
146
+ before :all do
147
+ @port = spec_helper_start_svc_app_in_thread_for( RSpecDatedAtAService )
148
+ spec_helper_start_svc_app_in_thread_for( RSpecDatedAtBService )
149
+ spec_helper_start_svc_app_in_thread_for( RSpecDatedAtCService )
150
+ end
151
+
152
+ def run_test_with_resource_options_boolean_of( value )
153
+ now = DateTime.now
154
+
155
+ data = {
156
+ :backdated_to => Hoodoo::Utilities.nanosecond_iso8601( now ),
157
+ :use_resource_options => value,
158
+ :thing_to_show_in_b => 'hello'
159
+ }
160
+
161
+ response = spec_helper_http(
162
+ klass: Net::HTTP::Post,
163
+ port: @port,
164
+ path: '/v1/rspec_dated_at_a',
165
+ body: data.to_json,
166
+ headers: { 'Content-Type' => 'application/json; charset=utf-8' }
167
+ )
168
+
169
+ expect( response.code ).to eq( '200' )
170
+ result = JSON.parse( response.body )
171
+
172
+ expect( result ).to eq( {
173
+ 'id' => 'hello_to_c',
174
+ 'dated_at' => now.to_s
175
+ } )
176
+ end
177
+
178
+ it 'passes dates through properly via context options' do
179
+ run_test_with_resource_options_boolean_of( true )
180
+ end
181
+
182
+ it 'passes dates through properly via endpoint options' do
183
+ run_test_with_resource_options_boolean_of( false )
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,560 @@
1
+ # Test coverage (mostly hypothetical, just to ensure no typing errors etc. via
2
+ # full code coverage) for esoteric/exotic communcation methods such as on-queue
3
+ # endpoints or HTTPS transport for local machine inter-resource calls.
4
+
5
+ require 'spec_helper'
6
+
7
+ describe Hoodoo::Services::Middleware do
8
+
9
+ class RSpecTestServiceExoticStubImplementation < Hoodoo::Services::Implementation
10
+ def list( context )
11
+ context.response.set_resources( [], 99 )
12
+ end
13
+ end
14
+
15
+ class RSpecTestServiceExoticStubInterface < Hoodoo::Services::Interface
16
+ interface :Version do
17
+ endpoint :version, RSpecTestServiceExoticStubImplementation
18
+ version 2
19
+ end
20
+ end
21
+
22
+ class RSpecTestServiceExoticStub < Hoodoo::Services::Service
23
+ comprised_of RSpecTestServiceExoticStubInterface
24
+ end
25
+
26
+ context 'on queue' do
27
+ before :each do
28
+ @old_queue = ENV[ 'AMQ_ENDPOINT' ]
29
+ ENV[ 'AMQ_ENDPOINT' ] = 'amqp://test:test@127.0.0.1'
30
+ @mw = Hoodoo::Services::Middleware.new( RSpecTestServiceExoticStub.new )
31
+
32
+ @cvar = false
33
+ if Hoodoo::Services::Middleware.class_variable_defined?( '@@alchemy' )
34
+ @cvar = true
35
+ @cvar_val = Hoodoo::Services::Middleware.class_variable_get( '@@alchemy' )
36
+ end
37
+
38
+ # Need to blow away the discoverer's local cache or the simulation will
39
+ # never attempt to run on queue.
40
+
41
+ discoverer = @mw.instance_variable_get( '@discoverer' )
42
+ discoverer.instance_variable_set( '@known_local_resources', {} ) # Hack for test!
43
+ end
44
+
45
+ after :each do
46
+ ENV[ 'AMQ_ENDPOINT' ] = @old_queue
47
+
48
+ if Hoodoo::Services::Middleware.class_variable_defined?( '@@alchemy' )
49
+ if @cvar == true
50
+ Hoodoo::Services::Middleware.class_variable_set( '@@alchemy', @cvar_val )
51
+ else
52
+ Hoodoo::Services::Middleware.remove_class_variable( '@@alchemy' )
53
+ end
54
+ end
55
+ end
56
+
57
+ it 'knows it is on-queue' do
58
+ expect( Hoodoo::Services::Middleware.on_queue? ).to eq( true )
59
+ end
60
+
61
+ # TODO: Weak test! Assumes static mappings. Will need modification
62
+ # for real Consul discoverer when implemented.
63
+ #
64
+ it 'returns known queue endpoint locations' do
65
+ location = @mw.instance_variable_get( '@discoverer' ).discover( :Version, 2 )
66
+ expect( location ).to be_a( Hoodoo::Services::Discovery::ForAMQP )
67
+ expect( location.queue_name ).to eq( 'service.version' )
68
+ expect( location.equivalent_path ).to eq( '/v2/versions' )
69
+ end
70
+
71
+ # TODO: Update for real Consul discoverer when implemented.
72
+ #
73
+ # it 'returns "nil" for unknown queue endpoint locations' do
74
+ # location = @mw.instance_variable_get( '@discoverer' ).discover( :NotAKnownResource, 2 )
75
+ # expect( location ).to be_nil
76
+ # end
77
+
78
+ context 'calling Alchemy' do
79
+ before :each do
80
+ mw = Hoodoo::Services::Middleware.new( RSpecTestServiceExoticStub.new )
81
+ @interaction = Hoodoo::Services::Middleware::Interaction.new(
82
+ {},
83
+ mw,
84
+ Hoodoo::Services::Middleware.test_session()
85
+ )
86
+ @interaction.target_interface = OpenStruct.new
87
+ @interaction.context.request.locale = 'fr'
88
+
89
+ @mock_alchemy = OpenStruct.new
90
+ Hoodoo::Services::Middleware.class_variable_set( '@@alchemy', @mock_alchemy )
91
+ end
92
+
93
+ def run_expectations( action, mock_queue, full_path, mock_method, mock_query, mock_remote, mock_response )
94
+ expect_any_instance_of( Hoodoo::Services::Discovery::ByConsul ).to receive(
95
+ :discover_remote
96
+ ).and_return(
97
+ mock_remote
98
+ )
99
+
100
+ if mock_query
101
+ mock_query = Hoodoo::Utilities.stringify( mock_query )
102
+ mock_query[ 'search' ] = URI.encode_www_form( mock_query[ 'search' ] ) if ( mock_query[ 'search' ].is_a?( ::Hash ) )
103
+ mock_query[ 'filter' ] = URI.encode_www_form( mock_query[ 'filter' ] ) if ( mock_query[ 'filter' ].is_a?( ::Hash ) )
104
+ end
105
+
106
+ expect( @mock_alchemy ).to receive( :http_request ).once do | queue, method, path, opts |
107
+ expect( queue ).to eq( mock_queue )
108
+ expect( method ).to eq( mock_method )
109
+ expect( path ).to eq( full_path )
110
+ expect( opts ).to eq(
111
+ {
112
+ :session_id => @interaction.context.session.session_id,
113
+ :host => 'localhost',
114
+ :port => 80,
115
+ :body => action == :create || action == :update ? '{}' : '',
116
+ :query => mock_query,
117
+ :headers => {
118
+ 'Content-Type' => 'application/json; charset=utf-8',
119
+ 'Content-Language' => 'fr',
120
+ 'Accept-Language' => 'fr',
121
+ 'X-Interaction-ID' => @interaction.interaction_id,
122
+ 'X-Session-ID' => @interaction.context.session.session_id
123
+ }
124
+ }
125
+ )
126
+ end.and_return( mock_response )
127
+ end
128
+
129
+ # All the other tests in this section run with @mw having no
130
+ # local services "as far as it is concerned" via the before-each
131
+ # code above. We want to make sure that service announcement when
132
+ # on-queue *does* announce locally (because at one point it did
133
+ # not, which was a bug) so this test provides that coverage.
134
+ #
135
+ it 'runs local discovery unless that is knocked out' do
136
+
137
+ # @mw has local discovery knocked out, so build a new
138
+ # one that doesn't. This will have the local discovery data
139
+ # available still.
140
+ #
141
+ @mw = Hoodoo::Services::Middleware.new( RSpecTestServiceExoticStub.new )
142
+
143
+ mock_queue = 'service.version'
144
+ mock_path = '/v2/version/'
145
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
146
+ resource: 'Version',
147
+ version: 2,
148
+ queue_name: mock_queue,
149
+ equivalent_path: mock_path
150
+ )
151
+
152
+ # We expect local discovery, so no discover_remote call.
153
+
154
+ expect_any_instance_of( Hoodoo::Services::Discovery::ByConsul ).to_not receive( :discover_remote )
155
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
156
+
157
+ # The endpoint should've been called locally; the implementation at
158
+ # the top of this file sets an empty array with dataset size 99.
159
+
160
+ mock_result = endpoint.list()
161
+ expect( mock_result ).to be_empty
162
+ expect( mock_result.dataset_size ).to eq( 99 )
163
+ end
164
+
165
+ it 'complains about a missing Alchemy instance' do
166
+ mock_queue = 'service.version'
167
+ mock_path = '/v2/version/'
168
+
169
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
170
+ resource: 'Version',
171
+ version: 2,
172
+ queue_name: mock_queue,
173
+ equivalent_path: mock_path
174
+ )
175
+
176
+ expect_any_instance_of( Hoodoo::Services::Discovery::ByConsul ).to receive(
177
+ :discover_remote
178
+ ).and_return(
179
+ mock_remote
180
+ )
181
+
182
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
183
+
184
+ # Fragile! Hack for test only.
185
+ endpoint.instance_variable_get( '@wrapped_endpoint' ).alchemy = nil
186
+
187
+ mock_response = AlchemyAMQ::HTTPResponse.new(
188
+ :status_code => 200,
189
+ :body => '{"_data":[]}'
190
+ )
191
+
192
+ expect {
193
+ mock_result = endpoint.list()
194
+ }.to raise_error( RuntimeError, 'Hoodoo::Client::Endpoint::AMQP cannot be used unless an Alchemy instance has been provided' )
195
+ end
196
+
197
+ it 'calls #list over Alchemy and handles 200' do
198
+ mock_queue = 'service.version'
199
+ mock_path = '/v2/version'
200
+ mock_method = 'GET'
201
+ mock_query = { :search => { :foo => :bar } }
202
+
203
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
204
+ resource: 'Version',
205
+ version: 2,
206
+ queue_name: mock_queue,
207
+ equivalent_path: mock_path
208
+ )
209
+
210
+ mock_response = AlchemyAMQ::HTTPResponse.new(
211
+ :status_code => 200,
212
+ :body => '{"_data":[]}'
213
+ )
214
+
215
+ run_expectations( :list, mock_queue, mock_path + '/', mock_method, mock_query, mock_remote, mock_response )
216
+
217
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
218
+ mock_result = endpoint.list( mock_query )
219
+
220
+ expect( mock_result ).to eq( Hoodoo::Client::AugmentedArray.new )
221
+ end
222
+
223
+ it 'calls #show over Alchemy and handles 200' do
224
+ mock_queue = 'service.version'
225
+ mock_path = '/v2/version'
226
+ mock_method = 'GET'
227
+ mock_query = { :search => { :foo => :bar } }
228
+
229
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
230
+ resource: 'Version',
231
+ version: 2,
232
+ queue_name: mock_queue,
233
+ equivalent_path: mock_path
234
+ )
235
+
236
+ mock_response = AlchemyAMQ::HTTPResponse.new(
237
+ :status_code => 200,
238
+ :body => '{}'
239
+ )
240
+
241
+ run_expectations( :show, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
242
+
243
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
244
+ mock_result = endpoint.show( 'ident', mock_query )
245
+
246
+ expect( mock_result ).to eq( Hoodoo::Client::AugmentedHash.new )
247
+ end
248
+
249
+ it 'calls #create over Alchemy and handles 200' do
250
+ mock_queue = 'service.version'
251
+ mock_path = '/v2/version'
252
+ mock_method = 'POST'
253
+ mock_query = { :search => { :foo => :bar } }
254
+
255
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
256
+ resource: 'Version',
257
+ version: 2,
258
+ queue_name: mock_queue,
259
+ equivalent_path: mock_path
260
+ )
261
+
262
+ mock_response = AlchemyAMQ::HTTPResponse.new(
263
+ :status_code => 200,
264
+ :body => '{}'
265
+ )
266
+
267
+ run_expectations( :create, mock_queue, mock_path + '/', mock_method, mock_query, mock_remote, mock_response )
268
+
269
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
270
+ mock_result = endpoint.create( {}, mock_query )
271
+
272
+ expect( mock_result ).to eq( Hoodoo::Client::AugmentedHash.new )
273
+ end
274
+
275
+ it 'calls #update over Alchemy and handles 200' do
276
+ mock_queue = 'service.version'
277
+ mock_path = '/v2/version'
278
+ mock_method = 'PATCH'
279
+ mock_query = { :search => { :foo => :bar } }
280
+
281
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
282
+ resource: 'Version',
283
+ version: 2,
284
+ queue_name: mock_queue,
285
+ equivalent_path: mock_path
286
+ )
287
+
288
+ mock_response = AlchemyAMQ::HTTPResponse.new(
289
+ :status_code => 200,
290
+ :body => '{}'
291
+ )
292
+
293
+ run_expectations( :update, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
294
+
295
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
296
+ mock_result = endpoint.update( 'ident', {}, mock_query )
297
+
298
+ expect( mock_result ).to eq( Hoodoo::Client::AugmentedHash.new )
299
+ end
300
+
301
+ it 'calls #delete over Alchemy and handles 200' do
302
+ mock_queue = 'service.version'
303
+ mock_path = '/v2/version'
304
+ mock_method = 'DELETE'
305
+ mock_query = { :search => { :foo => :bar } }
306
+
307
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
308
+ resource: 'Version',
309
+ version: 2,
310
+ queue_name: mock_queue,
311
+ equivalent_path: mock_path
312
+ )
313
+
314
+ mock_response = AlchemyAMQ::HTTPResponse.new(
315
+ :status_code => 200,
316
+ :body => '{}'
317
+ )
318
+
319
+ run_expectations( :delete, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
320
+
321
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
322
+ mock_result = endpoint.delete( 'ident', mock_query )
323
+
324
+ expect( mock_result ).to eq( Hoodoo::Client::AugmentedHash.new )
325
+ end
326
+
327
+ it 'calls #show over Alchemy and handles 408' do
328
+ mock_queue = 'service.version'
329
+ mock_path = '/v2/version'
330
+ mock_method = 'GET'
331
+ mock_query = { :search => { :foo => :bar } }
332
+
333
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
334
+ resource: 'Version',
335
+ version: 2,
336
+ queue_name: mock_queue,
337
+ equivalent_path: mock_path
338
+ )
339
+
340
+ mock_response = AlchemyAMQ::HTTPResponse.new(
341
+ :status_code => 408,
342
+ :body => '408 Timeout'
343
+ )
344
+
345
+ run_expectations( :show, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
346
+
347
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
348
+ mock_result = endpoint.show( 'ident', mock_query )
349
+
350
+ expect( mock_result ).to be_a( Hoodoo::Client::AugmentedHash )
351
+ expect( mock_result.platform_errors ).to_not be_nil
352
+
353
+ errors = mock_result.platform_errors.render( Hoodoo::UUID.generate )
354
+
355
+ expect( errors ).to have_key( 'errors' )
356
+ expect( errors[ 'errors' ] ).to be_a( Array )
357
+ expect( errors[ 'errors' ][ 0 ] ).to have_key( 'code' )
358
+ expect( errors[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.timeout' )
359
+ end
360
+
361
+ it 'calls #show over Alchemy and handles 404' do
362
+ mock_queue = 'service.version'
363
+ mock_path = '/v2/version'
364
+ mock_method = 'GET'
365
+ mock_query = { :search => { :foo => :bar } }
366
+
367
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
368
+ resource: 'Version',
369
+ version: 2,
370
+ queue_name: mock_queue,
371
+ equivalent_path: mock_path
372
+ )
373
+
374
+ mock_response = AlchemyAMQ::HTTPResponse.new(
375
+ :status_code => 404,
376
+ :body => '404 Not Found'
377
+ )
378
+
379
+ run_expectations( :show, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
380
+
381
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
382
+ mock_result = endpoint.show( 'ident', mock_query )
383
+
384
+ expect( mock_result ).to be_a( Hoodoo::Client::AugmentedHash )
385
+ expect( mock_result.platform_errors ).to_not be_nil
386
+
387
+ errors = mock_result.platform_errors.render( Hoodoo::UUID.generate )
388
+
389
+ expect( errors ).to have_key( 'errors' )
390
+ expect( errors[ 'errors' ] ).to be_a( Array )
391
+ expect( errors[ 'errors' ][ 0 ] ).to have_key( 'code' )
392
+ expect( errors[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.not_found' )
393
+ end
394
+
395
+ it 'calls #show over Alchemy and handles "unexpected" status codes' do
396
+ mock_queue = 'service.version'
397
+ mock_path = '/v2/version'
398
+ mock_method = 'GET'
399
+ mock_query = { :search => { :foo => :bar } }
400
+
401
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
402
+ resource: 'Version',
403
+ version: 2,
404
+ queue_name: mock_queue,
405
+ equivalent_path: mock_path
406
+ )
407
+
408
+ bad_body_data = '499 Invented'
409
+ mock_response = AlchemyAMQ::HTTPResponse.new(
410
+ :status_code => 499,
411
+ :body => bad_body_data
412
+ )
413
+
414
+ run_expectations( :show, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
415
+
416
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
417
+ mock_result = endpoint.show( 'ident', mock_query )
418
+
419
+ expect( mock_result ).to be_a( Hoodoo::Client::AugmentedHash )
420
+ expect( mock_result.platform_errors ).to_not be_nil
421
+
422
+ errors = mock_result.platform_errors.render( Hoodoo::UUID.generate )
423
+
424
+ expect( errors ).to have_key( 'errors' )
425
+ expect( errors[ 'errors' ] ).to be_a( Array )
426
+ expect( errors[ 'errors' ][ 0 ] ).to_not be_nil
427
+
428
+ expect( errors[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.fault' )
429
+ expect( errors[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'Unexpected raw HTTP status code 499 with non-JSON response' )
430
+ expect( errors[ 'errors' ][ 0 ][ 'reference' ] ).to eq( "#{ bad_body_data }" )
431
+ end
432
+
433
+ it 'calls #show over Alchemy and handles 200 status but bad JSON' do
434
+ mock_queue = 'service.version'
435
+ mock_path = '/v2/version'
436
+ mock_method = 'GET'
437
+ mock_query = { :search => { :foo => :bar } }
438
+
439
+ mock_remote = Hoodoo::Services::Discovery::ForAMQP.new(
440
+ resource: 'Version',
441
+ version: 2,
442
+ queue_name: mock_queue,
443
+ equivalent_path: mock_path
444
+ )
445
+
446
+ bad_body_data = 'Not JSON'
447
+ mock_response = AlchemyAMQ::HTTPResponse.new(
448
+ :status_code => 200,
449
+ :body => bad_body_data
450
+ )
451
+
452
+ run_expectations( :show, mock_queue, mock_path + '/ident', mock_method, mock_query, mock_remote, mock_response )
453
+
454
+ endpoint = @mw.inter_resource_endpoint_for( 'Version', 2, @interaction )
455
+ mock_result = endpoint.show( 'ident', mock_query )
456
+
457
+ expect( mock_result ).to be_a( Hoodoo::Client::AugmentedHash )
458
+ expect( mock_result.platform_errors ).to_not be_nil
459
+
460
+ errors = mock_result.platform_errors.render( Hoodoo::UUID.generate )
461
+
462
+ expect( errors ).to have_key( 'errors' )
463
+ expect( errors[ 'errors' ] ).to be_a( Array )
464
+ expect( errors[ 'errors' ][ 0 ] ).to_not be_nil
465
+
466
+ expect( errors[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.fault' )
467
+ expect( errors[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'Could not parse body data returned from inter-resource call despite receiving HTTP status code 200' )
468
+ expect( errors[ 'errors' ][ 0 ][ 'reference' ] ).to eq( "#{ bad_body_data }" )
469
+ end
470
+ end
471
+ end
472
+
473
+ context 'over HTTPS' do
474
+ before :all do
475
+ @port = spec_helper_start_svc_app_in_thread_for(
476
+ RSpecTestServiceExoticStub,
477
+ true # Use SSL
478
+ )
479
+ end
480
+
481
+ # In random order the test after this might work first, but the idea here
482
+ # is just to make a normal Net::HTTP request over SSL to the service using
483
+ # the appropriate spec_helper.rb support method, just to see if it's awake.
484
+ # If not, we don't expect the middleware's internal routines to manage it!
485
+ #
486
+ it 'demonstrates working HTTPS generally' do
487
+ response = spec_helper_http(
488
+ port: @port,
489
+ path: '/v2/version',
490
+ ssl: true
491
+ )
492
+
493
+ expect( response.code ).to eq( '200' )
494
+ end
495
+
496
+ # This is the *real* internal test, though done via calling down into the
497
+ # middleware's private implementation rather than worrying about standing
498
+ # up two services and making a 'real' inter-resource call across them.
499
+ # That's done elsewhere over HTTP. We just want to know if HTTPS functions
500
+ # at all from that same chunk of code here.
501
+ #
502
+ it 'attempts HTTPS communication' do
503
+
504
+ # Set up a middleware instance and mock interaction
505
+
506
+ mw = Hoodoo::Services::Middleware.new( RSpecTestServiceExoticStub.new )
507
+ interaction = Hoodoo::Services::Middleware::Interaction.new(
508
+ {},
509
+ mw,
510
+ Hoodoo::Services::Middleware.test_session()
511
+ )
512
+ interaction.target_interface = OpenStruct.new
513
+
514
+ # Synthesise an HTTP(S) discovery result for 'Version' / v2 and use
515
+ # it to build an HTTP(S) endpoint.
516
+
517
+ mock_wrapped_discovery_result = Hoodoo::Services::Discovery::ForHTTP.new(
518
+ resource: 'Version',
519
+ version: 2,
520
+ endpoint_uri: URI.parse( "https://127.0.0.1:#{ @port }/v2/version" ),
521
+ ca_file: 'spec/files/ca/ca-cert.pem'
522
+ )
523
+
524
+ mock_wrapped_endpoint = Hoodoo::Client::Endpoint::HTTP.new(
525
+ 'Version',
526
+ 2,
527
+ :session => Hoodoo::Services::Middleware.test_session(),
528
+ :discovery_result => mock_wrapped_discovery_result
529
+ )
530
+
531
+ # Synthesise a remote resource discovery result for the HTTP(S) endpoint
532
+ # built above and use that to make a remote call endpoint.
533
+
534
+ discovery_result = Hoodoo::Services::Discovery::ForRemote.new(
535
+ :resource => 'Version',
536
+ :version => 2,
537
+ :wrapped_endpoint => mock_wrapped_endpoint
538
+ )
539
+
540
+ endpoint = Hoodoo::Services::Middleware::InterResourceRemote.new(
541
+ 'Version',
542
+ 2,
543
+ {
544
+ :interaction => interaction,
545
+ :discovery_result => discovery_result
546
+ }
547
+ )
548
+
549
+ # Use the endpoint.
550
+
551
+ mock_result = endpoint.list()
552
+
553
+ # Expect an empty *array* back, with dataset size. A Hash implies an error.
554
+
555
+ expect( mock_result ).to eq( Hoodoo::Client::AugmentedArray.new )
556
+ expect( mock_result.dataset_size ).to eq(99)
557
+ expect( mock_result.platform_errors.has_errors? ).to eq( false )
558
+ end
559
+ end
560
+ end