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,432 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hoodoo::Services::Session do
4
+
5
+ before :each do
6
+ Hoodoo::Services::Session::MockDalliClient.reset()
7
+ end
8
+
9
+ it 'initialises with default options' do
10
+ s = described_class.new()
11
+ expect( s.created_at ).to be_a( Time )
12
+ expect( Hoodoo::UUID.valid?( s.session_id ) ).to eq( true )
13
+ expect( s.memcached_host ).to be_nil
14
+ expect( s.caller_id ).to be_nil
15
+ expect( s.caller_version ).to eq( 0 )
16
+ end
17
+
18
+ it 'initialises with given options' do
19
+ s = described_class.new(
20
+ :session_id => '1234',
21
+ :memcached_host => 'abcd',
22
+ :caller_id => '0987',
23
+ :caller_version => 2
24
+ )
25
+ expect( s.created_at ).to be_a( Time )
26
+ expect( s.session_id ).to eq( '1234' )
27
+ expect( s.memcached_host ).to eq( 'abcd' )
28
+ expect( s.caller_id ).to eq( '0987' )
29
+ expect( s.caller_version ).to eq( 2 )
30
+ end
31
+
32
+ it 'reports not expired when it has no expiry' do
33
+ s = described_class.new
34
+ expect( s.expired? ).to eq( false )
35
+ end
36
+
37
+ it 'knows if expired' do
38
+ s = described_class.new
39
+ s.instance_variable_set( '@expires_at', Time.now - 1 )
40
+ expect( s.expired? ).to eq( true )
41
+ end
42
+
43
+ it 'converts to a Hash' do
44
+ s = described_class.new(
45
+ :session_id => '1234',
46
+ :memcached_host => 'abcd',
47
+ :caller_id => '0987',
48
+ :caller_version => 2
49
+ )
50
+ p = Hoodoo::Services::Permissions.new
51
+
52
+ s.permissions = p
53
+ s.identity = { 'foo' => 'foo', 'bar' => 'bar' }
54
+ s.scoping = { 'baz' => [ 'foo', 'bar', 'baz' ] }
55
+
56
+ h = s.to_h
57
+
58
+ expect( h ).to eq( {
59
+ 'session_id' => '1234',
60
+ 'caller_id' => '0987',
61
+ 'caller_version' => 2,
62
+
63
+ 'created_at' => s.created_at.iso8601,
64
+
65
+ 'identity' => { 'foo' => 'foo', 'bar' => 'bar' },
66
+ 'scoping' => { 'baz' => [ 'foo', 'bar', 'baz' ] },
67
+ 'permissions' => p.to_h()
68
+ } )
69
+ end
70
+
71
+ it 'reads from a Hash' do
72
+ s = described_class.new
73
+ p = Hoodoo::Services::Permissions.new
74
+ c = Time.now.utc
75
+ e = Time.now.utc + 10
76
+ h = {
77
+ 'session_id' => '1234',
78
+ 'caller_id' => '0987',
79
+ 'caller_version' => 2,
80
+
81
+ 'created_at' => c.iso8601,
82
+ 'expires_at' => e.iso8601,
83
+
84
+ 'identity' => { 'foo' => 'foo', 'bar' => 'bar' },
85
+ 'scoping' => { 'baz' => [ 'foo', 'bar', 'baz' ] },
86
+ 'permissions' => p.to_h()
87
+ }
88
+
89
+ s.from_h!( h )
90
+
91
+ expect( s.session_id ).to eq( '1234' )
92
+ expect( s.caller_id ).to eq( '0987' )
93
+ expect( s.caller_version ).to eq( 2 )
94
+ expect( s.created_at ).to eq( Time.parse( c.iso8601 ) )
95
+ expect( s.expires_at ).to eq( Time.parse( e.iso8601 ) )
96
+ expect( s.identity.foo ).to eq( 'foo' )
97
+ expect( s.identity.bar ).to eq( 'bar' )
98
+ expect( s.scoping.baz ).to eq( [ 'foo', 'bar', 'baz' ] )
99
+ expect( s.permissions.to_h ).to eq( p.to_h )
100
+ end
101
+
102
+ it 'saves/loads to/from Memcached' do
103
+ s1 = described_class.new(
104
+ :session_id => '1234',
105
+ :memcached_host => 'abcd',
106
+ :caller_id => '0987',
107
+ :caller_version => 2
108
+ )
109
+
110
+ expect( s1.save_to_memcached ).to eq( :ok )
111
+
112
+ store = Hoodoo::Services::Session::MockDalliClient.store()
113
+ expect( store[ '1234' ] ).to_not be_nil
114
+ expect( store[ '0987' ] ).to eq( { :expires_at => nil, :value => { 'version' => 2 } } )
115
+
116
+ # Check that session gains an expiry time when saved.
117
+ #
118
+ expect( s1.expires_at ).to be_a( Time )
119
+
120
+ # Ensure "created_at" is significantly different in the next session.
121
+ # This is important to reliably detect session load failures in testing
122
+ # given "#iso8601()" time resolution limits and so-on.
123
+ #
124
+ sleep( 0.2 )
125
+
126
+ s2 = described_class.new
127
+ expect( s2.load_from_memcached!( s1.session_id ) ).to eq( :ok )
128
+
129
+ expect( s2.created_at ).to eq( Time.parse( s1.created_at.iso8601 ) )
130
+ expect( s2.expires_at ).to eq( Time.parse( s1.expires_at.iso8601 ) )
131
+ expect( s2.session_id ).to eq( s1.session_id )
132
+ expect( s2.memcached_host ).to be_nil
133
+ expect( s2.caller_id ).to eq( s1.caller_id )
134
+ expect( s2.caller_version ).to eq( s1.caller_version )
135
+ end
136
+
137
+ it 'refuses to save if a newer caller version is present' do
138
+ expect( described_class ).to receive( :connect_to_memcached ).twice.and_return( Hoodoo::Services::Session::MockDalliClient.new )
139
+
140
+ # Save a session with a high caller version
141
+
142
+ s1 = described_class.new(
143
+ :session_id => '1234',
144
+ :memcached_host => 'abcd',
145
+ :caller_id => '0987',
146
+ :caller_version => 4
147
+ )
148
+
149
+ expect( s1.save_to_memcached ).to eq( :ok )
150
+
151
+ # Save another with, initially, a lower caller version. The idea here
152
+ # is that session creation is underway when a caller gets updated.
153
+
154
+ s2 = described_class.new(
155
+ :session_id => '2345',
156
+ :memcached_host => 'abcd',
157
+ :caller_id => '0987',
158
+ :caller_version => 3
159
+ )
160
+
161
+ expect( s2.save_to_memcached ).to eq( :outdated )
162
+ end
163
+
164
+ it 'invalidates a session if the client ID advances during its lifetime' do
165
+ expect( described_class ).to receive( :connect_to_memcached ).exactly( 4 ).times.and_return( Hoodoo::Services::Session::MockDalliClient.new )
166
+ loader = described_class.new
167
+
168
+ # Save a session with a low caller version.
169
+
170
+ s1 = described_class.new(
171
+ :session_id => '1234',
172
+ :memcached_host => 'abcd',
173
+ :caller_id => '0987',
174
+ :caller_version => 1
175
+ )
176
+
177
+ expect( s1.save_to_memcached ).to eq( :ok )
178
+
179
+ # Save another with a higher caller version.
180
+
181
+ s2 = described_class.new(
182
+ :session_id => '2345',
183
+ :memcached_host => 'abcd',
184
+ :caller_id => '0987',
185
+ :caller_version => 2
186
+ )
187
+
188
+ expect( s2.save_to_memcached ).to eq( :ok )
189
+
190
+ # Should not be able to load the first one anymore.
191
+
192
+ expect( loader.load_from_memcached!( '1234' ) ).to eq( :outdated )
193
+ expect( loader.load_from_memcached!( '2345' ) ).to eq( :ok )
194
+ end
195
+
196
+ it 'refuses to load if the caller version is outdated' do
197
+ expect( described_class ).to receive( :connect_to_memcached ).exactly( 5 ).times.and_return( Hoodoo::Services::Session::MockDalliClient.new )
198
+ loader = described_class.new
199
+
200
+ # Save a session with a low caller version
201
+
202
+ s1 = described_class.new(
203
+ :session_id => '1234',
204
+ :memcached_host => 'abcd',
205
+ :caller_id => '0987',
206
+ :caller_version => 1
207
+ )
208
+
209
+ expect( s1.save_to_memcached ).to eq( :ok )
210
+
211
+ # Should be able to load it back.
212
+
213
+ expect( loader.load_from_memcached!( '1234' ) ).to eq( :ok )
214
+
215
+ # Save another with a higher caller version.
216
+
217
+ s2 = described_class.new(
218
+ :session_id => '2345',
219
+ :memcached_host => 'abcd',
220
+ :caller_id => '0987',
221
+ :caller_version => 2
222
+ )
223
+
224
+ expect( s2.save_to_memcached ).to eq( :ok )
225
+
226
+ # Try to load the first one again; should fail.
227
+
228
+ expect( loader.load_from_memcached!( '1234' ) ).to eq( :outdated )
229
+
230
+ # The newer one should load OK.
231
+
232
+ expect( loader.load_from_memcached!( '2345' ) ).to eq( :ok )
233
+ end
234
+
235
+ it 'refuses to load if expired' do
236
+ expect( described_class ).to receive( :connect_to_memcached ).twice.and_return( Hoodoo::Services::Session::MockDalliClient.new )
237
+ loader = described_class.new
238
+
239
+ # Save a session with a high caller version
240
+
241
+ s = described_class.new(
242
+ :session_id => '1234',
243
+ :memcached_host => 'abcd',
244
+ :caller_id => '0987',
245
+ :caller_version => 1
246
+ )
247
+
248
+ expect( s ).to receive( :to_h ).and_wrap_original do | obj, args |
249
+ h = obj.call( *args )
250
+ h[ 'expires_at' ] = ( Time.now - 1 ).utc.iso8601
251
+ h
252
+ end
253
+
254
+ expect( s.save_to_memcached ).to eq( :ok )
255
+ expect( loader.load_from_memcached!( '1234' ) ).to eq( :outdated )
256
+ end
257
+
258
+ it 'can explicitly update a caller' do
259
+ s = described_class.new(
260
+ :session_id => '1234',
261
+ :memcached_host => 'abcd',
262
+ :caller_id => '0987',
263
+ :caller_version => 1
264
+ )
265
+
266
+ expect( described_class ).to receive( :connect_to_memcached ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
267
+
268
+ expect( s.update_caller_version_in_memcached( '9944', 23 ) ).to eq( :ok )
269
+ expect( s.update_caller_version_in_memcached( 'abcd', 2, Hoodoo::Services::Session::MockDalliClient.new ) ).to eq( :ok )
270
+
271
+ store = Hoodoo::Services::Session::MockDalliClient.store()
272
+ expect( store[ '9944' ] ).to eq( { :expires_at => nil, :value => { 'version' => 23 } } )
273
+ expect( store[ 'abcd' ] ).to eq( { :expires_at => nil, :value => { 'version' => 2 } } )
274
+ end
275
+
276
+ it 'handles invalid session IDs when loading' do
277
+ expect( described_class ).to receive( :connect_to_memcached ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
278
+ loader = described_class.new
279
+ expect( loader.load_from_memcached!( '1234' ) ).to eq( :not_found )
280
+ end
281
+
282
+ it 'complains if there is no caller ID' do
283
+ s = described_class.new
284
+ expect {
285
+ s.save_to_memcached()
286
+ }.to raise_error RuntimeError
287
+ end
288
+
289
+ it 'logs Memcached exceptions when loading' do
290
+ loader = described_class.new
291
+
292
+ expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :get ).once do
293
+ raise 'Mock Memcached connection failure'
294
+ end
295
+
296
+ expect( Hoodoo::Services::Middleware.logger ).to receive( :warn ).once.and_call_original
297
+ expect( loader.load_from_memcached!( '1234' ) ).to eq( :fail )
298
+ end
299
+
300
+ it 'logs Memcached exceptions when updating caller version during session saving' do
301
+ s = described_class.new(
302
+ :caller_id => '0987',
303
+ :caller_version => 1
304
+ )
305
+
306
+ expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :set ).once do
307
+ raise 'Mock Memcached connection failure'
308
+ end
309
+
310
+ expect( Hoodoo::Services::Middleware.logger ).to receive( :warn ).once.and_call_original
311
+ expect( s.save_to_memcached() ).to eq( :fail )
312
+ end
313
+
314
+ it 'logs Memcached exceptions when saving session' do
315
+ s = described_class.new(
316
+ :caller_id => '0987',
317
+ :caller_version => 1
318
+ )
319
+
320
+ expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :set ).once.and_call_original
321
+ expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :set ).once do
322
+ raise 'Mock Memcached connection failure'
323
+ end
324
+
325
+ expect( Hoodoo::Services::Middleware.logger ).to receive( :warn ).once.and_call_original
326
+ expect( s.save_to_memcached() ).to eq( :fail )
327
+ end
328
+
329
+ it 'can be deleted' do
330
+ s = described_class.new(
331
+ :session_id => '1234',
332
+ :memcached_host => 'abcd',
333
+ :caller_id => '0987',
334
+ :caller_version => 1
335
+ )
336
+
337
+ s.save_to_memcached
338
+
339
+ expect{ s.delete_from_memcached }.to change{ s.load_from_memcached!( s.session_id ) }.from( :ok ).to( :not_found )
340
+ end
341
+
342
+ it 'handles attempts to delete not-found things' do
343
+ s = described_class.new(
344
+ :session_id => '1234',
345
+ :memcached_host => 'abcd',
346
+ :caller_id => '0987',
347
+ :caller_version => 1
348
+ )
349
+
350
+ expect( s.delete_from_memcached ).to eq( :not_found )
351
+ end
352
+
353
+ it 'logs and reports deletion exceptions' do
354
+ fdc = Hoodoo::Services::Session::MockDalliClient.new
355
+ s = described_class.new(
356
+ :session_id => '1234',
357
+ :memcached_host => 'abcd',
358
+ :caller_id => '0987',
359
+ :caller_version => 1
360
+ )
361
+
362
+ s.save_to_memcached
363
+
364
+ allow( described_class ).to receive( :connect_to_memcached ) {
365
+ raise 'Intentional exception'
366
+ }
367
+
368
+ expect( Hoodoo::Services::Middleware.logger ).to receive( :warn )
369
+ expect( s.delete_from_memcached ).to eq( :fail )
370
+ end
371
+
372
+ # We really can't do this without insisting on testers having a
373
+ # Memcached instance; instead, assume Dalli works (!) and mock it.
374
+ #
375
+ context 'real Memcached connection code test coverage' do
376
+ before :example do
377
+ # Clear the connection cache for each test
378
+ Hoodoo::Services::Session.class_variable_set( '@@dalli_clients', nil ) # Hack for test!
379
+ Hoodoo::Services::Session::MockDalliClient.bypass( true )
380
+ end
381
+
382
+ after :example do
383
+ Hoodoo::Services::Session::MockDalliClient.bypass( false )
384
+ end
385
+
386
+ it 'complains about a missing host' do
387
+ expect {
388
+ described_class.connect_to_memcached( nil )
389
+ }.to raise_error RuntimeError
390
+
391
+ expect {
392
+ described_class.connect_to_memcached( '' )
393
+ }.to raise_error RuntimeError
394
+ end
395
+
396
+ it 'tries to connect' do
397
+ expect_any_instance_of( Dalli::Client ).to receive( :stats ).and_return( {} )
398
+ expect( described_class.connect_to_memcached( '256.2.3.4:0' ) ).to be_a( Dalli::Client )
399
+ end
400
+
401
+ it 'handles connection failures' do
402
+ expect_any_instance_of( Dalli::Client ).to receive( :stats ).and_return( nil )
403
+ expect {
404
+ described_class.connect_to_memcached( '256.2.3.4:0' )
405
+ }.to raise_error RuntimeError
406
+ end
407
+
408
+ it 'handles connection exceptions' do
409
+ expect_any_instance_of( Dalli::Client ).to receive( :initialize ) do
410
+ raise 'Mock Memcached connection failure'
411
+ end
412
+
413
+ expect {
414
+ described_class.connect_to_memcached( '256.2.3.4:0' )
415
+ }.to raise_error RuntimeError
416
+ end
417
+
418
+ it 'only initialises once for one given host' do
419
+ expect( Dalli::Client ).to receive( :new ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
420
+
421
+ 1.upto( 3 ) do
422
+ described_class.connect_to_memcached( 'one' )
423
+ end
424
+
425
+ expect( Dalli::Client ).to receive( :new ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
426
+
427
+ 1.upto( 3 ) do
428
+ described_class.connect_to_memcached( 'two' )
429
+ end
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,298 @@
1
+ require 'webrick'
2
+ require 'webrick/https'
3
+
4
+ # Set the correct environment for testing.
5
+
6
+ ENV[ 'RACK_ENV' ] = 'test'
7
+
8
+ # Configure the code coverage analyser.
9
+
10
+ require 'simplecov'
11
+ require 'simplecov-rcov'
12
+
13
+ SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
14
+ SimpleCov.start do
15
+ add_filter '/spec/'
16
+ end
17
+
18
+ # Debugging support.
19
+
20
+ require 'byebug'
21
+
22
+ # The ActiveRecord extensions need testing in the context of a database. I
23
+ # did consider NullDB - https://github.com/nulldb/nulldb - but this was too
24
+ # far from 'the real thing' for my liking. Instead, we use SQLite in memory
25
+ # and DatabaseCleaner to reset state between tests.
26
+ #
27
+ # http://stackoverflow.com/questions/7586813/fake-an-active-record-model-without-db
28
+
29
+ require 'database_cleaner'
30
+ require 'active_record'
31
+ require 'logger'
32
+
33
+ # Include AlchemyAMQ for testing only.
34
+
35
+ # TODO: See spec/alchemy/alchemy-amq.rb. Remove this once 'real' Alchemy
36
+ # is opened.
37
+ #
38
+ $LOAD_PATH.unshift File.join( File.dirname( __FILE__ ), 'alchemy' )
39
+
40
+ begin
41
+ require 'alchemy-amq'
42
+ rescue LoadError
43
+ raise 'Cannot load alchemy-amq; did you run me with "bundle exec..." ?'
44
+ end
45
+
46
+ # Now it's safe to require Rack test code and Hoodoo itself.
47
+
48
+ require 'rack/test'
49
+ require 'hoodoo'
50
+
51
+ RSpec.configure do | config |
52
+
53
+ # Provides familiar-ish 'get'/'post' etc. DSL for simulated URL fetch tests.
54
+
55
+ config.include Rack::Test::Methods
56
+
57
+ # http://stackoverflow.com/questions/1819614/how-do-i-globally-configure-rspec-to-keep-the-color-and-format-specdoc-o
58
+ #
59
+ # Use color in STDOUT,
60
+ # use color not only in STDOUT but also in pagers and files.
61
+
62
+ config.color = true
63
+ config.tty = true
64
+
65
+ # Run specs in random order to surface order dependencies. If you find an
66
+ # order dependency and want to debug it, you can fix the order by providing
67
+ # the seed, which is printed after each run.
68
+ # --seed 1234
69
+
70
+ config.order = :random
71
+
72
+ # Seed global randomization in this process using the `--seed` CLI option.
73
+ # Setting this allows you to use `--seed` to deterministically reproduce
74
+ # test failures related to randomization by passing the same `--seed` value
75
+ # as the one that triggered the failure.
76
+
77
+ Kernel.srand config.seed
78
+
79
+ # Wake up Database Cleaner.
80
+
81
+ DatabaseCleaner.strategy = :transaction # MUST NOT be changed
82
+
83
+ database_name = 'hoodoo_test'
84
+
85
+ config.before( :suite ) do
86
+
87
+ # Redirect $stderr before each test so a test log gets written without
88
+ # disturbing the RSpec terminal output; make sure the session system is
89
+ # in "test mode"; make sure we get a unique DRb daemon instance for the
90
+ # tests, which we can shut down afterwards.
91
+
92
+ base_path = File.join( File.dirname( __FILE__ ), '..', 'log' )
93
+ log = File.new( File.join( base_path, 'test.log' ), 'a+' )
94
+
95
+ $stderr.reopen(log)
96
+
97
+ $stderr << "\n" << "*"*80 << "\n"
98
+ $stderr << Time.now.to_s << "\n"
99
+ $stderr << "*"*80 << "\n\n"
100
+
101
+ Hoodoo::Services::Middleware.set_log_folder( base_path )
102
+
103
+ ENV[ 'HOODOO_DISCOVERY_BY_DRB_PORT_OVERRIDE' ] = Hoodoo::Utilities.spare_port().to_s()
104
+
105
+ # Connect to PostgreSQL before tests start.
106
+
107
+ spec_helper_connect_to_postgres()
108
+
109
+ # Sometimes if a user force quits the spec suite, the hoodoo_test database
110
+ # will not be deleted. The following makes sure it is removed now.
111
+
112
+ database_exists = ActiveRecord::Base.connection.execute(
113
+ "SELECT COUNT(*) FROM pg_database WHERE datname = '#{ database_name }'"
114
+ ).any?
115
+
116
+ if database_exists
117
+ ActiveRecord::Base.connection.drop_database( database_name )
118
+ end
119
+
120
+ # Create the test database (hiding output) and connect to it.
121
+
122
+ ActiveRecord::Base.logger = Logger.new( nil )
123
+ ActiveRecord::Base.connection.create_database( database_name )
124
+ ActiveRecord::Base.logger = Logger.new( STDERR )
125
+
126
+ spec_helper_connect_to_postgres( database_name )
127
+
128
+ end
129
+
130
+ config.after( :suite ) do
131
+
132
+ # Delete the test database afterwards. Must disconnect from it
133
+ # first, going back to the default 'postgresql' database instead.
134
+
135
+ begin
136
+ spec_helper_connect_to_postgres()
137
+ ActiveRecord::Base.connection.drop_database( database_name )
138
+ rescue
139
+ # Ignore exceptions. Any failures to delete the database will
140
+ # be picked up by the at-test-startup precautionary delete
141
+ # anyway.
142
+ end
143
+
144
+ # Shut down the DRb discoverer.
145
+
146
+ begin
147
+ drb_uri = Hoodoo::Services::Discovery::ByDRb::DRbServer.uri()
148
+ drb_service = DRbObject.new_with_uri( drb_uri )
149
+ drb_service.stop()
150
+ rescue
151
+ # Ignore exceptions. For test subsets or depending on test order,
152
+ # there might not be a DRb service to shut down.
153
+ end
154
+
155
+ end
156
+
157
+ # Make sure DatabaseCleaner runs between each test.
158
+
159
+ config.before( :each ) do
160
+ DatabaseCleaner.start
161
+ end
162
+
163
+ config.after( :each ) do
164
+ DatabaseCleaner.clean
165
+ end
166
+ end
167
+
168
+ # Connect to PostgreSQL for test purposes. Generally only used within
169
+ # the "spec_helper.rb" file for environment setup and teardown.
170
+ #
171
+ # +database_name+:: Name of database to which a connection should be
172
+ # established. If omitted, connects to engine default
173
+ # of 'postgres'.
174
+ #
175
+ def spec_helper_connect_to_postgres( database_name = 'postgres' )
176
+ ActiveRecord::Base.establish_connection(
177
+ :adapter => 'postgresql',
178
+ :username => ENV[ 'DATABASE_USER' ],
179
+ :database => database_name
180
+ )
181
+ end
182
+
183
+ # For things like ActiveRecord::Migrations used during database-orientated
184
+ # tests, or for certain logger tests, have to silence +STDOUT+ chatter to
185
+ # avoid messing up RSpec's output, but we need to be sure it's restored.
186
+ #
187
+ # &block:: Block of code to call while +STDOUT+ is disabled.
188
+ #
189
+ def spec_helper_silence_stdout( &block )
190
+ Kernel.silence_stream( STDOUT, &block )
191
+ end
192
+
193
+ # Start up a service application under WEBrick via Rack on localhost, using
194
+ # any available free port. The server is run in a Ruby Thread and *cannot be
195
+ # killed* once started. It will only exit when the entire calling process shuts
196
+ # down.
197
+ #
198
+ # Only returns once the server is running and accepting connections. Returns
199
+ # the port number upon which the server is listening.
200
+ #
201
+ # +app_class+:: Hoodoo::Services::Service subclass for the service to start.
202
+ #
203
+ # +use_ssl+:: If +true+, SSL self-signed certificates in +spec/files+ are
204
+ # used to support SSL testing, provided certificate chain
205
+ # verification is bypassed. Optional; default is +false+, which
206
+ # uses normal HTTP.
207
+ #
208
+ def spec_helper_start_svc_app_in_thread_for( app_class, use_ssl = false, app_options = {} )
209
+
210
+ port = Hoodoo::Utilities.spare_port()
211
+
212
+ Thread.start do
213
+ app = Rack::Builder.new do
214
+ use Hoodoo::Services::Middleware unless app_options[:skip_hoodoo_middleware]
215
+ run app_class.new
216
+ end
217
+
218
+ options = {
219
+ :app => app,
220
+ :Port => port,
221
+ :Host => '127.0.0.1',
222
+ :server => :webrick
223
+ }
224
+
225
+ if ( use_ssl )
226
+ pem = File.join( File.dirname( __FILE__ ), 'files', 'ssl.pem' )
227
+ key = File.join( File.dirname( __FILE__ ), 'files', 'ssl.key' )
228
+
229
+ options.merge!( {
230
+ :SSLEnable => true,
231
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open( pem ).read ),
232
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open( key ).read ),
233
+ :SSLCertName => [ [ "CN", WEBrick::Utils::getservername() ] ]
234
+ } )
235
+ end
236
+
237
+ # This command never returns. Since this server usually brings up the
238
+ # service application before anything else happens (dependent upon the
239
+ # exact call order of a test, but it's always true at the time of writing),
240
+ # this is the application which will also run a local DRb server.
241
+
242
+ begin
243
+ Rack::Server.start( options )
244
+ rescue => e
245
+ puts "TEST SERVER FAILURE: #{e.inspect}"
246
+ puts e.backtrace
247
+ end
248
+ end
249
+
250
+ # Wait for the server to come up. I tried many approaches. In the end,
251
+ # only this hacky polling-talk-to-server code worked reliably.
252
+
253
+ repeat = true
254
+
255
+ while repeat
256
+ begin
257
+ spec_helper_http( path: '/', port: port, ssl: use_ssl )
258
+ repeat = false
259
+ rescue Errno::ECONNREFUSED
260
+ sleep 0.1
261
+ end
262
+ end
263
+
264
+ return port
265
+ end
266
+
267
+ # Run an HTTP request on localhost and return the result as a
268
+ # +Net::HTTP::Response+ instance.
269
+ #
270
+ # +path+:: URI path _including_ leading "/".
271
+ # +port+:: Port number (String or Integer)
272
+ # +ssl+:: (Optional) +true+ to use HTTPS, else HTTP
273
+ # +klass+:: (Optional) Class to use for request, default +Net::HTTP::Get+
274
+ # +body+:: (Optional) Body for request (POST/PATCH only), default +nil+
275
+ # +headers+:: (Optional) Header name/value Hash, default empty.
276
+ #
277
+ def spec_helper_http( path:,
278
+ port:,
279
+ ssl: false,
280
+ klass: Net::HTTP::Get,
281
+ body: nil,
282
+ headers: {} )
283
+
284
+ headers = { 'Content-Type' => 'application/json; charset=utf-8' }.merge( headers )
285
+ remote_uri = URI.parse( "http://127.0.0.1:#{ port }#{ path }" )
286
+ http = Net::HTTP.new( remote_uri.host, remote_uri.port )
287
+ request = klass.new( remote_uri.request_uri() )
288
+
289
+ if ( ssl )
290
+ http.use_ssl = true
291
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
292
+ end
293
+
294
+ request.initialize_http_header( headers )
295
+ request.body = body unless body.nil?
296
+
297
+ return http.request( request )
298
+ end