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.
- checksums.yaml +7 -0
- data/bin/hoodoo +5 -0
- data/lib/hoodoo.rb +27 -0
- data/lib/hoodoo/active.rb +32 -0
- data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
- data/lib/hoodoo/active/active_record/base.rb +81 -0
- data/lib/hoodoo/active/active_record/creator.rb +134 -0
- data/lib/hoodoo/active/active_record/dated.rb +343 -0
- data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
- data/lib/hoodoo/active/active_record/finder.rb +606 -0
- data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
- data/lib/hoodoo/active/active_record/secure.rb +431 -0
- data/lib/hoodoo/active/active_record/support.rb +106 -0
- data/lib/hoodoo/active/active_record/translated.rb +87 -0
- data/lib/hoodoo/active/active_record/uuid.rb +80 -0
- data/lib/hoodoo/active/active_record/writer.rb +321 -0
- data/lib/hoodoo/client.rb +23 -0
- data/lib/hoodoo/client/augmented_array.rb +29 -0
- data/lib/hoodoo/client/augmented_base.rb +168 -0
- data/lib/hoodoo/client/augmented_hash.rb +23 -0
- data/lib/hoodoo/client/client.rb +354 -0
- data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
- data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
- data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
- data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
- data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
- data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
- data/lib/hoodoo/client/headers.rb +269 -0
- data/lib/hoodoo/communicators.rb +23 -0
- data/lib/hoodoo/communicators/fast.rb +44 -0
- data/lib/hoodoo/communicators/pool.rb +601 -0
- data/lib/hoodoo/communicators/slow.rb +84 -0
- data/lib/hoodoo/data.rb +51 -0
- data/lib/hoodoo/data/resources/caller.rb +39 -0
- data/lib/hoodoo/data/resources/errors.rb +28 -0
- data/lib/hoodoo/data/resources/log.rb +31 -0
- data/lib/hoodoo/data/resources/session.rb +26 -0
- data/lib/hoodoo/data/types/error_primitive.rb +27 -0
- data/lib/hoodoo/data/types/permissions.rb +40 -0
- data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
- data/lib/hoodoo/data/types/permissions_full.rb +28 -0
- data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
- data/lib/hoodoo/discovery.rb +20 -0
- data/lib/hoodoo/errors.rb +19 -0
- data/lib/hoodoo/errors/error_descriptions.rb +229 -0
- data/lib/hoodoo/errors/errors.rb +322 -0
- data/lib/hoodoo/generator.rb +139 -0
- data/lib/hoodoo/logger.rb +23 -0
- data/lib/hoodoo/logger/fast_writer.rb +27 -0
- data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
- data/lib/hoodoo/logger/logger.rb +387 -0
- data/lib/hoodoo/logger/slow_writer.rb +49 -0
- data/lib/hoodoo/logger/writer_mixin.rb +52 -0
- data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
- data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
- data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
- data/lib/hoodoo/middleware.rb +33 -0
- data/lib/hoodoo/presenters.rb +45 -0
- data/lib/hoodoo/presenters/base.rb +281 -0
- data/lib/hoodoo/presenters/base_dsl.rb +519 -0
- data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
- data/lib/hoodoo/presenters/embedding.rb +232 -0
- data/lib/hoodoo/presenters/types/array.rb +118 -0
- data/lib/hoodoo/presenters/types/boolean.rb +26 -0
- data/lib/hoodoo/presenters/types/date.rb +26 -0
- data/lib/hoodoo/presenters/types/date_time.rb +26 -0
- data/lib/hoodoo/presenters/types/decimal.rb +47 -0
- data/lib/hoodoo/presenters/types/enum.rb +55 -0
- data/lib/hoodoo/presenters/types/field.rb +158 -0
- data/lib/hoodoo/presenters/types/float.rb +26 -0
- data/lib/hoodoo/presenters/types/hash.rb +361 -0
- data/lib/hoodoo/presenters/types/integer.rb +26 -0
- data/lib/hoodoo/presenters/types/object.rb +117 -0
- data/lib/hoodoo/presenters/types/string.rb +53 -0
- data/lib/hoodoo/presenters/types/tags.rb +24 -0
- data/lib/hoodoo/presenters/types/text.rb +26 -0
- data/lib/hoodoo/presenters/types/uuid.rb +54 -0
- data/lib/hoodoo/services.rb +34 -0
- data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
- data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
- data/lib/hoodoo/services/discovery/discovery.rb +186 -0
- data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
- data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
- data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
- data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
- data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
- data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
- data/lib/hoodoo/services/middleware/interaction.rb +127 -0
- data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
- data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
- data/lib/hoodoo/services/services/context.rb +153 -0
- data/lib/hoodoo/services/services/implementation.rb +132 -0
- data/lib/hoodoo/services/services/interface.rb +934 -0
- data/lib/hoodoo/services/services/permissions.rb +250 -0
- data/lib/hoodoo/services/services/request.rb +189 -0
- data/lib/hoodoo/services/services/response.rb +316 -0
- data/lib/hoodoo/services/services/service.rb +141 -0
- data/lib/hoodoo/services/services/session.rb +729 -0
- data/lib/hoodoo/utilities.rb +12 -0
- data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
- data/lib/hoodoo/utilities/utilities.rb +380 -0
- data/lib/hoodoo/utilities/uuid.rb +44 -0
- data/lib/hoodoo/version.rb +17 -0
- data/spec/active/active_record/base_spec.rb +57 -0
- data/spec/active/active_record/creator_spec.rb +88 -0
- data/spec/active/active_record/dated_spec.rb +248 -0
- data/spec/active/active_record/error_mapping_spec.rb +360 -0
- data/spec/active/active_record/finder_spec.rb +744 -0
- data/spec/active/active_record/search_helper_spec.rb +384 -0
- data/spec/active/active_record/secure_spec.rb +435 -0
- data/spec/active/active_record/support_spec.rb +225 -0
- data/spec/active/active_record/translated_spec.rb +19 -0
- data/spec/active/active_record/uuid_spec.rb +72 -0
- data/spec/active/active_record/writer_spec.rb +272 -0
- data/spec/alchemy/alchemy-amq.rb +33 -0
- data/spec/client/augmented_array_spec.rb +15 -0
- data/spec/client/augmented_base_spec.rb +50 -0
- data/spec/client/augmented_hash_spec.rb +15 -0
- data/spec/client/client_spec.rb +955 -0
- data/spec/client/endpoint/endpoint_spec.rb +70 -0
- data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
- data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
- data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
- data/spec/client/headers_spec.rb +172 -0
- data/spec/communicators/fast_spec.rb +9 -0
- data/spec/communicators/pool_spec.rb +339 -0
- data/spec/communicators/slow_spec.rb +15 -0
- data/spec/data/resources/caller_spec.rb +156 -0
- data/spec/data/resources/errors_spec.rb +22 -0
- data/spec/data/resources/log_spec.rb +20 -0
- data/spec/data/resources/session_spec.rb +15 -0
- data/spec/data/types/error_primitive_spec.rb +15 -0
- data/spec/data/types/permissions_defaults_spec.rb +25 -0
- data/spec/data/types/permissions_full_spec.rb +44 -0
- data/spec/data/types/permissions_resources_spec.rb +34 -0
- data/spec/data/types/permissions_spec.rb +37 -0
- data/spec/errors/error_descriptions_spec.rb +98 -0
- data/spec/errors/errors_spec.rb +346 -0
- data/spec/integration/service_actions_spec.rb +112 -0
- data/spec/logger/fast_writer_spec.rb +18 -0
- data/spec/logger/logger_spec.rb +259 -0
- data/spec/logger/slow_writer_spec.rb +144 -0
- data/spec/logger/writers/file_writer_spec.rb +37 -0
- data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
- data/spec/logger/writers/stream_writer_spec.rb +38 -0
- data/spec/presenters/base_dsl_spec.rb +111 -0
- data/spec/presenters/base_spec.rb +871 -0
- data/spec/presenters/common_resource_fields_spec.rb +30 -0
- data/spec/presenters/embedding_spec.rb +87 -0
- data/spec/presenters/types/array_spec.rb +249 -0
- data/spec/presenters/types/boolean_spec.rb +51 -0
- data/spec/presenters/types/date_spec.rb +57 -0
- data/spec/presenters/types/date_time_spec.rb +59 -0
- data/spec/presenters/types/decimal_spec.rb +58 -0
- data/spec/presenters/types/enum_spec.rb +71 -0
- data/spec/presenters/types/field_spec.rb +77 -0
- data/spec/presenters/types/float_spec.rb +50 -0
- data/spec/presenters/types/hash_spec.rb +1069 -0
- data/spec/presenters/types/integer_spec.rb +50 -0
- data/spec/presenters/types/object_spec.rb +177 -0
- data/spec/presenters/types/string_spec.rb +65 -0
- data/spec/presenters/types/tags_spec.rb +56 -0
- data/spec/presenters/types/text_spec.rb +50 -0
- data/spec/presenters/types/uuid_spec.rb +46 -0
- data/spec/presenters/walk_spec.rb +198 -0
- data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
- data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
- data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
- data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
- data/spec/services/discovery/discovery_spec.rb +73 -0
- data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
- data/spec/services/discovery/results/for_http_spec.rb +37 -0
- data/spec/services/discovery/results/for_local_spec.rb +21 -0
- data/spec/services/discovery/results/for_remote_spec.rb +15 -0
- data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
- data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
- data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
- data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
- data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
- data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
- data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
- data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
- data/spec/services/middleware/middleware_cors_spec.rb +93 -0
- data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
- data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
- data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
- data/spec/services/middleware/middleware_logging_spec.rb +356 -0
- data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
- data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
- data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
- data/spec/services/middleware/middleware_public_spec.rb +238 -0
- data/spec/services/middleware/middleware_spec.rb +1569 -0
- data/spec/services/middleware/string_inquirer_spec.rb +30 -0
- data/spec/services/services/application_spec.rb +74 -0
- data/spec/services/services/context_spec.rb +48 -0
- data/spec/services/services/implementation_spec.rb +45 -0
- data/spec/services/services/interface_spec.rb +262 -0
- data/spec/services/services/permissions_spec.rb +249 -0
- data/spec/services/services/request_spec.rb +95 -0
- data/spec/services/services/response_spec.rb +250 -0
- data/spec/services/services/session_spec.rb +432 -0
- data/spec/spec_helper.rb +298 -0
- data/spec/utilities/utilities_spec.rb +537 -0
- data/spec/utilities/uuid_spec.rb +20 -0
- 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
|