-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathsuite_endpoint.rb
329 lines (289 loc) · 9.82 KB
/
suite_endpoint.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
require 'hanami/controller'
require 'rack/request'
require_relative '../ext/rack'
module Inferno
module DSL
# A base class for creating endpoints to test client requests. This class is
# based on Hanami::Action, and may be used similarly to [a normal Hanami
# endpoint](https://github.com/hanami/controller/tree/v2.0.0).
#
# @example
# class AuthorizedEndpoint < Inferno::DSL::SuiteEndpoint
# # Identify the incoming request based on a bearer token
# def test_run_identifier
# request.headers['authorization']&.delete_prefix('Bearer ')
# end
#
# # Return a json FHIR Patient resource
# def make_response
# response.status = 200
# response.body = FHIR::Patient.new(id: 'abcdef').to_json
# response.format = :json
# end
#
# # Update the waiting test to pass when the incoming request is received.
# # This will resume the test run.
# def update_result
# results_repo.update(result.id, result: 'pass')
# end
#
# # Apply the 'authorized' tag to the incoming request so that it may be
# # used by later tests.
# def tags
# ['authorized']
# end
# end
#
# class AuthorizedRequestSuite < Inferno::TestSuite
# id :authorized_suite
# suite_endpoint :get, '/authorized_endpoint', AuthorizedEndpoint
#
# group do
# title 'Authorized Request Group'
#
# test do
# title 'Wait for authorized request'
#
# input :bearer_token
#
# run do
# wait(
# identifier: bearer_token,
# message: "Waiting to receive a request with bearer_token: #{bearer_token}" \
# "at `#{Inferno::Application['base_url']}/custom/authorized_suite/authorized_endpoint`"
# )
# end
# end
# end
# end
class SuiteEndpoint < Hanami::Action
attr_reader :req, :res
# @!group Overrides These methods should be overridden by subclasses to
# define the behavior of the endpoint
# Override this method to determine a test run's identifier based on an
# incoming request.
#
# @return [String]
#
# @example
# def test_run_identifier
# # Identify the test session of an incoming request based on the bearer
# # token
# request.headers['authorization']&.delete_prefix('Bearer ')
# end
def test_run_identifier
nil
end
# Override this method to build the response.
#
# @return [Void]
#
# @example
# def make_response
# response.status = 200
# response.body = { abc: 123 }.to_json
# response.format = :json
# end
def make_response
nil
end
# Override this method to define the tags which will be applied to the
# request.
#
# @return [Array<String>]
def tags
@tags ||= []
end
# Override this method to assign a name to the request
#
# @return [String]
def name
result&.runnable&.incoming_request_name
end
# Override this method to update the current waiting result. To resume the
# test run, set the result to something other than 'waiting'.
#
# @return [Void]
#
# @example
# def update_result
# results_repo.update(result.id, result: 'pass')
# end
def update_result
nil
end
# Override this method to specify whether this request should be
# persisted. Defaults to true.
#
# @return [Boolean]
def persist_request?
true
end
# @!endgroup
# @private
def self.call(...)
new.call(...)
end
# @return [Inferno::Repositories::Requests]
def requests_repo
@requests_repo ||= Inferno::Repositories::Requests.new
end
# @return [Inferno::Repositories::Results]
def results_repo
@results_repo ||= Inferno::Repositories::Results.new
end
# @return [Inferno::Repositories::TestRuns]
def test_runs_repo
@test_runs_repo ||= Inferno::Repositories::TestRuns.new
end
# @return [Inferno::Repositories::Tests]
def tests_repo
@tests_repo ||= Inferno::Repositories::Tests.new
end
# @private
def initialize(config: self.class.config) # rubocop:disable Lint/MissingSuper
@config = config
end
# The incoming request as a `Hanami::Action::Request`
#
# @return [Hanami::Action::Request]
#
# @example
# request.params # Get url/query params
# request.body.read # Get body
# request.headers['accept'] # Get Accept header
def request
req
end
# The response as a `Hanami::Action::Response`. Modify this to build the
# response to the incoming request.
#
# @return [Hanami::Action::Response]
#
# @example
# response.status = 200 # Set the status
# response.body = 'Ok' # Set the body
# # Set headers
# response.headers.merge!('X-Custom-Header' => 'CUSTOM_HEADER_VALUE')
def response
res
end
# The test run which is waiting for incoming requests
#
# @return [Inferno::Entities::TestRun]
def test_run
@test_run ||=
test_runs_repo.find_latest_waiting_by_identifier(find_test_run_identifier).tap do |test_run|
halt 500, "Unable to find test run with identifier '#{test_run_identifier}'." if test_run.nil?
end
end
# The result which is waiting for incoming requests for the current test
# run
#
# @return [Inferno::Entities::Result]
def result
@result ||= find_result
end
# The test which is currently waiting for incoming requests
#
# @return [Inferno::Entities::Test]
def test
@test ||= tests_repo.find(result.test_id)
end
# @return [Logger] Inferno's logger
def logger
@logger ||= Application['logger']
end
# @private
def find_test_run_identifier
@test_run_identifier ||= test_run_identifier
rescue StandardError => e
halt 500, "Unable to determine test run identifier:\n#{e.full_message}"
end
# @private
def find_result
results_repo.find_waiting_result(test_run_id: test_run.id)
end
# @private
# The actual persisting happens in
# Inferno::Utils::Middleware::RequestRecorder, which allows the response
# to include response headers added by other parts of the rack stack
# rather than only the response headers explicitly added in the endpoint.
def persist_request
req.env['inferno.test_session_id'] = test_run.test_session_id
req.env['inferno.result_id'] = result.id
req.env['inferno.tags'] = tags
req.env['inferno.name'] = name if name.present?
add_persistence_callback
end
# @private
def resume_test_run?
find_result&.result != 'wait'
end
# @private
# Inferno::Utils::Middleware::RequestRecorder actually resumes the
# TestRun. If it were resumed here, it would be resuming prior to the
# Request being persisted.
def resume
req.env['inferno.resume_test_run'] = true
req.env['inferno.test_run_id'] = test_run.id
end
# @private
def handle(req, res)
@req = req
@res = res
test_run
persist_request if persist_request?
update_result
resume if resume_test_run?
make_response
rescue StandardError => e
halt 500, e.full_message
end
# @private
def add_persistence_callback # rubocop:disable Metrics/CyclomaticComplexity
logger = Application['logger']
env = req.env
env['rack.after_reply'] ||= []
env['rack.after_reply'] << proc do
repo = Inferno::Repositories::Requests.new
uri = URI('http://example.com')
uri.scheme = env['rack.url_scheme']
uri.host = env['SERVER_NAME']
uri.port = env['SERVER_PORT']
uri.path = env['REQUEST_PATH'] || ''
uri.query = env['rack.request.query_string'] if env['rack.request.query_string'].present?
url = uri&.to_s
verb = env['REQUEST_METHOD']
request_body = env['rack.input']
request_body.rewind if env['rack.input'].respond_to? :rewind
request_body = request_body.instance_of?(Puma::NullIO) ? nil : request_body.string
request_headers = ::Rack::Request.new(env).headers.to_h.map { |name, value| { name:, value: } }
status, response_headers, response_body = env['inferno.response']
response_headers = response_headers.map { |name, value| { name:, value: } }
repo.create(
verb:,
url:,
direction: 'incoming',
name: env['inferno.name'],
status:,
request_body:,
response_body: response_body.join,
result_id: env['inferno.result_id'],
test_session_id: env['inferno.test_session_id'],
request_headers:,
response_headers:,
tags: env['inferno.tags']
)
if env['inferno.resume_test_run']
test_run_id = env['inferno.test_run_id']
Inferno::Repositories::TestRuns.new.mark_as_no_longer_waiting(test_run_id)
Inferno::Jobs.perform(Jobs::ResumeTestRun, test_run_id)
end
rescue StandardError => e
logger.error(e.full_message)
end
end
end
end
end