inferno_core 0.4.22 → 0.4.24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/inferno/apps/cli/services.rb +1 -1
- data/lib/inferno/config/boot/validator.rb +19 -0
- data/lib/inferno/db/migrations/009_add_request_tags.rb +18 -0
- data/lib/inferno/db/schema.rb +19 -0
- data/lib/inferno/dsl/fhir_client.rb +50 -24
- data/lib/inferno/dsl/fhir_resource_validation.rb +299 -0
- data/lib/inferno/dsl/http_client.rb +12 -8
- data/lib/inferno/dsl/request_storage.rb +15 -3
- data/lib/inferno/dsl/resume_test_route.rb +7 -1
- data/lib/inferno/dsl/runnable.rb +4 -2
- data/lib/inferno/dsl.rb +3 -1
- data/lib/inferno/entities/request.rb +23 -7
- data/lib/inferno/entities/test_suite.rb +1 -0
- data/lib/inferno/jobs/invoke_validator_session.rb +23 -0
- data/lib/inferno/jobs.rb +1 -0
- data/lib/inferno/public/bundle.js +2 -2
- data/lib/inferno/repositories/requests.rb +98 -12
- data/lib/inferno/repositories/tags.rb +18 -0
- data/lib/inferno/version.rb +1 -1
- data/spec/factories/request.rb +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab9c01a985f9ceb43cc9ad79ddb604cc246b19dfa39f09b323c7677ed347aa88
|
4
|
+
data.tar.gz: 05aa6e68ea46177b1fab155052e1c84fe7c09368eca5ada5f9a5d933ff270a07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2879c429dea880db81928b84a0b17afbc06dab2dc2dae2bd088918ac0e25e594b13b12b1bd4f01943c698c382acd1f85ae07a2b14b0b8eea07484e23cc0eb8e
|
7
|
+
data.tar.gz: 5bc9bcf0b1006acf2fd46962d0e69609f343b480c88b832f1465382001029dc5b97353fb80ae3dd22c9c32e375012a290b585f9064eabb2e22816855b9d51612
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Inferno::Application.boot(:validator) do
|
2
|
+
init do
|
3
|
+
use :suites
|
4
|
+
|
5
|
+
# This process should only run once, to start one job per validator,
|
6
|
+
# so skipping it on workers will start it only once from the "web" process
|
7
|
+
next if Sidekiq.server?
|
8
|
+
|
9
|
+
Inferno::Repositories::TestSuites.new.all.each do |suite|
|
10
|
+
suite.fhir_validators.each do |name, validators|
|
11
|
+
validators.each_with_index do |validator, index|
|
12
|
+
if validator.is_a? Inferno::DSL::FHIRResourceValidation::Validator
|
13
|
+
Inferno::Jobs.perform(Inferno::Jobs::InvokeValidatorSession, suite.id, name.to_s, index)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table :tags do
|
4
|
+
column :id, String, primary_key: true, null: false, size: 36
|
5
|
+
column :name, String, size: 255, null: false
|
6
|
+
|
7
|
+
index :name, unique: true
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :requests_tags do
|
11
|
+
foreign_key :tags_id, :tags, index: true, type: String, null: false, size: 36, key: [:id]
|
12
|
+
foreign_key :requests_id, :requests, index: true, type: Integer, null: false, key: [:index]
|
13
|
+
|
14
|
+
index [:tags_id, :requests_id], unique: true
|
15
|
+
index [:requests_id, :tags_id], unique: true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/inferno/db/schema.rb
CHANGED
@@ -4,6 +4,15 @@ Sequel.migration do
|
|
4
4
|
Integer :version, :default=>0, :null=>false
|
5
5
|
end
|
6
6
|
|
7
|
+
create_table(:tags, :ignore_index_errors=>true) do
|
8
|
+
String :id, :size=>36, :null=>false
|
9
|
+
String :name, :size=>255, :null=>false
|
10
|
+
|
11
|
+
primary_key [:id]
|
12
|
+
|
13
|
+
index [:name], :unique=>true
|
14
|
+
end
|
15
|
+
|
7
16
|
create_table(:test_sessions) do
|
8
17
|
String :id, :size=>36, :null=>false
|
9
18
|
String :test_suite_id, :size=>255, :null=>false
|
@@ -121,5 +130,15 @@ Sequel.migration do
|
|
121
130
|
index [:requests_id]
|
122
131
|
index [:results_id]
|
123
132
|
end
|
133
|
+
|
134
|
+
create_table(:requests_tags, :ignore_index_errors=>true) do
|
135
|
+
foreign_key :tags_id, :tags, :type=>String, :size=>36, :null=>false, :key=>[:id]
|
136
|
+
foreign_key :requests_id, :requests, :null=>false, :key=>[:index]
|
137
|
+
|
138
|
+
index [:requests_id]
|
139
|
+
index [:requests_id, :tags_id], :unique=>true
|
140
|
+
index [:tags_id]
|
141
|
+
index [:tags_id, :requests_id], :unique=>true
|
142
|
+
end
|
124
143
|
end
|
125
144
|
end
|
@@ -101,9 +101,18 @@ module Inferno
|
|
101
101
|
# other tests
|
102
102
|
# @param headers [Hash] custom headers for this operation
|
103
103
|
# @param operation_method [Symbol] indicates which request type to use for the operation
|
104
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
104
105
|
# @return [Inferno::Entities::Request]
|
105
|
-
def fhir_operation(
|
106
|
-
|
106
|
+
def fhir_operation(
|
107
|
+
path,
|
108
|
+
body: nil,
|
109
|
+
client: :default,
|
110
|
+
name: nil,
|
111
|
+
headers: {},
|
112
|
+
operation_method: :post,
|
113
|
+
tags: []
|
114
|
+
)
|
115
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
107
116
|
tcp_exception_handler do
|
108
117
|
operation_headers = fhir_client(client).fhir_headers
|
109
118
|
operation_headers.merge!('Content-Type' => 'application/fhir+json') if body.present?
|
@@ -127,9 +136,10 @@ module Inferno
|
|
127
136
|
# @param client [Symbol]
|
128
137
|
# @param name [Symbol] Name for this request to allow it to be used by
|
129
138
|
# other tests
|
139
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
130
140
|
# @return [Inferno::Entities::Request]
|
131
|
-
def fhir_get_capability_statement(client: :default, name: nil)
|
132
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
141
|
+
def fhir_get_capability_statement(client: :default, name: nil, tags: [])
|
142
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
133
143
|
tcp_exception_handler do
|
134
144
|
fhir_client(client).conformance_statement
|
135
145
|
fhir_client(client).reply
|
@@ -143,9 +153,10 @@ module Inferno
|
|
143
153
|
# @param client [Symbol]
|
144
154
|
# @param name [Symbol] Name for this request to allow it to be used by
|
145
155
|
# other tests
|
156
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
146
157
|
# @return [Inferno::Entities::Request]
|
147
|
-
def fhir_create(resource, client: :default, name: nil)
|
148
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
158
|
+
def fhir_create(resource, client: :default, name: nil, tags: [])
|
159
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
149
160
|
tcp_exception_handler do
|
150
161
|
fhir_client(client).create(resource)
|
151
162
|
end
|
@@ -159,9 +170,10 @@ module Inferno
|
|
159
170
|
# @param client [Symbol]
|
160
171
|
# @param name [Symbol] Name for this request to allow it to be used by
|
161
172
|
# other tests
|
173
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
162
174
|
# @return [Inferno::Entities::Request]
|
163
|
-
def fhir_read(resource_type, id, client: :default, name: nil)
|
164
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
175
|
+
def fhir_read(resource_type, id, client: :default, name: nil, tags: [])
|
176
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
165
177
|
tcp_exception_handler do
|
166
178
|
fhir_client(client).read(fhir_class_from_resource_type(resource_type), id)
|
167
179
|
end
|
@@ -176,9 +188,10 @@ module Inferno
|
|
176
188
|
# @param client [Symbol]
|
177
189
|
# @param name [Symbol] Name for this request to allow it to be used by
|
178
190
|
# other tests
|
191
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
179
192
|
# @return [Inferno::Entities::Request]
|
180
|
-
def fhir_vread(resource_type, id, version_id, client: :default, name: nil)
|
181
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
193
|
+
def fhir_vread(resource_type, id, version_id, client: :default, name: nil, tags: [])
|
194
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
182
195
|
tcp_exception_handler do
|
183
196
|
fhir_client(client).vread(fhir_class_from_resource_type(resource_type), id, version_id)
|
184
197
|
end
|
@@ -192,9 +205,10 @@ module Inferno
|
|
192
205
|
# @param client [Symbol]
|
193
206
|
# @param name [Symbol] Name for this request to allow it to be used by
|
194
207
|
# other tests
|
208
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
195
209
|
# @return [Inferno::Entities::Request]
|
196
|
-
def fhir_update(resource, id, client: :default, name: nil)
|
197
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
210
|
+
def fhir_update(resource, id, client: :default, name: nil, tags: [])
|
211
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
198
212
|
tcp_exception_handler do
|
199
213
|
fhir_client(client).update(resource, id)
|
200
214
|
end
|
@@ -209,9 +223,10 @@ module Inferno
|
|
209
223
|
# @param client [Symbol]
|
210
224
|
# @param name [Symbol] Name for this request to allow it to be used by
|
211
225
|
# other tests
|
226
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
212
227
|
# @return [Inferno::Entities::Request]
|
213
|
-
def fhir_patch(resource_type, id, patchset, client: :default, name: nil)
|
214
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
228
|
+
def fhir_patch(resource_type, id, patchset, client: :default, name: nil, tags: [])
|
229
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
215
230
|
tcp_exception_handler do
|
216
231
|
fhir_client(client).partial_update(fhir_class_from_resource_type(resource_type), id, patchset)
|
217
232
|
end
|
@@ -225,9 +240,10 @@ module Inferno
|
|
225
240
|
# @param client [Symbol]
|
226
241
|
# @param name [Symbol] Name for this request to allow it to be used by
|
227
242
|
# other tests
|
243
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
228
244
|
# @return [Inferno::Entities::Request]
|
229
|
-
def fhir_history(resource_type = nil, id = nil, client: :default, name: nil)
|
230
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
245
|
+
def fhir_history(resource_type = nil, id = nil, client: :default, name: nil, tags: [])
|
246
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
231
247
|
tcp_exception_handler do
|
232
248
|
if id
|
233
249
|
fhir_client(client).resource_instance_history(fhir_class_from_resource_type(resource_type), id)
|
@@ -248,8 +264,16 @@ module Inferno
|
|
248
264
|
# @param name [Symbol] Name for this request to allow it to be used by
|
249
265
|
# other tests
|
250
266
|
# @param search_method [Symbol] Use `:post` to search via POST
|
267
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
251
268
|
# @return [Inferno::Entities::Request]
|
252
|
-
def fhir_search(
|
269
|
+
def fhir_search(
|
270
|
+
resource_type = nil,
|
271
|
+
client: :default,
|
272
|
+
params: {},
|
273
|
+
name: nil,
|
274
|
+
search_method: :get,
|
275
|
+
tags: []
|
276
|
+
)
|
253
277
|
search =
|
254
278
|
if search_method == :post
|
255
279
|
{ body: params }
|
@@ -257,7 +281,7 @@ module Inferno
|
|
257
281
|
{ parameters: params }
|
258
282
|
end
|
259
283
|
|
260
|
-
store_request_and_refresh_token(fhir_client(client), name) do
|
284
|
+
store_request_and_refresh_token(fhir_client(client), name, tags) do
|
261
285
|
tcp_exception_handler do
|
262
286
|
if resource_type
|
263
287
|
fhir_client(client)
|
@@ -276,9 +300,10 @@ module Inferno
|
|
276
300
|
# @param client [Symbol]
|
277
301
|
# @param name [Symbol] Name for this request to allow it to be used by
|
278
302
|
# other tests
|
303
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
279
304
|
# @return [Inferno::Entities::Request]
|
280
|
-
def fhir_delete(resource_type, id, client: :default, name: nil)
|
281
|
-
store_request('outgoing', name) do
|
305
|
+
def fhir_delete(resource_type, id, client: :default, name: nil, tags: [])
|
306
|
+
store_request('outgoing', name:, tags:) do
|
282
307
|
tcp_exception_handler do
|
283
308
|
fhir_client(client).destroy(fhir_class_from_resource_type(resource_type), id)
|
284
309
|
end
|
@@ -291,9 +316,10 @@ module Inferno
|
|
291
316
|
# @param client [Symbol]
|
292
317
|
# @param name [Symbol] Name for this request to allow it to be used by
|
293
318
|
# other tests
|
319
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
294
320
|
# @return [Inferno::Entities::Request]
|
295
|
-
def fhir_transaction(bundle = nil, client: :default, name: nil)
|
296
|
-
store_request('outgoing', name) do
|
321
|
+
def fhir_transaction(bundle = nil, client: :default, name: nil, tags: [])
|
322
|
+
store_request('outgoing', name:, tags:) do
|
297
323
|
tcp_exception_handler do
|
298
324
|
fhir_client(client).transaction_bundle = bundle if bundle.present?
|
299
325
|
fhir_client(client).end_transaction
|
@@ -312,8 +338,8 @@ module Inferno
|
|
312
338
|
# expired. It's combined with `store_request` so that all of the fhir
|
313
339
|
# request methods don't have to be wrapped twice.
|
314
340
|
# @private
|
315
|
-
def store_request_and_refresh_token(client, name, &block)
|
316
|
-
store_request('outgoing', name) do
|
341
|
+
def store_request_and_refresh_token(client, name, tags, &block)
|
342
|
+
store_request('outgoing', name:, tags:) do
|
317
343
|
perform_refresh(client) if client.need_to_refresh? && client.able_to_refresh?
|
318
344
|
block.call
|
319
345
|
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require_relative '../ext/fhir_models'
|
2
|
+
module Inferno
|
3
|
+
module DSL
|
4
|
+
# This module contains the methods needed to configure a validator to
|
5
|
+
# perform validation of FHIR resources. The actual validation is performed
|
6
|
+
# by an external FHIR validation service. Tests will typically rely on
|
7
|
+
# `assert_valid_resource` for validation rather than directly calling
|
8
|
+
# methods on a validator.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# validator do
|
13
|
+
# url 'http://example.com/validator'
|
14
|
+
# exclude_message { |message| message.type == 'info' }
|
15
|
+
# perform_additional_validation do |resource, profile_url|
|
16
|
+
# if something_is_wrong
|
17
|
+
# { type: 'error', message: 'something is wrong' }
|
18
|
+
# else
|
19
|
+
# { type: 'info', message: 'everything is ok' }
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
module FHIRResourceValidation
|
24
|
+
def self.included(klass)
|
25
|
+
klass.extend ClassMethods
|
26
|
+
end
|
27
|
+
|
28
|
+
class Validator
|
29
|
+
attr_reader :requirements
|
30
|
+
attr_accessor :session_id
|
31
|
+
|
32
|
+
# @private
|
33
|
+
def initialize(requirements = nil, &)
|
34
|
+
instance_eval(&)
|
35
|
+
@requirements = requirements
|
36
|
+
end
|
37
|
+
|
38
|
+
# @private
|
39
|
+
def default_validator_url
|
40
|
+
ENV.fetch('FHIR_RESOURCE_VALIDATOR_URL')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set the url of the validator service
|
44
|
+
#
|
45
|
+
# @param validator_url [String]
|
46
|
+
def url(validator_url = nil)
|
47
|
+
@url = validator_url if validator_url
|
48
|
+
|
49
|
+
@url
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set the IGs that the validator will need to load
|
53
|
+
# Example: ["hl7.fhir.us.core#4.0.0"]
|
54
|
+
# @param igs [Array<String>]
|
55
|
+
def igs(validator_igs = nil)
|
56
|
+
@igs = validator_igs if validator_igs
|
57
|
+
|
58
|
+
@igs
|
59
|
+
end
|
60
|
+
|
61
|
+
# @private
|
62
|
+
def additional_validations
|
63
|
+
@additional_validations ||= []
|
64
|
+
end
|
65
|
+
|
66
|
+
# Perform validation steps in addition to FHIR validation.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# perform_additional_validation do |resource, profile_url|
|
70
|
+
# if something_is_wrong
|
71
|
+
# { type: 'error', message: 'something is wrong' }
|
72
|
+
# else
|
73
|
+
# { type: 'info', message: 'everything is ok' }
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
# @yieldparam resource [FHIR::Model] the resource being validated
|
77
|
+
# @yieldparam profile_url [String] the profile the resource is being
|
78
|
+
# validated against
|
79
|
+
# @yieldreturn [Array<Hash<Symbol, String>>,Hash<Symbol, String>] The
|
80
|
+
# block should return a Hash or an Array of Hashes if any validation
|
81
|
+
# messages should be added. The Hash must contain two keys: `:type`
|
82
|
+
# and `:message`. `:type` can have a value of `'info'`, `'warning'`,
|
83
|
+
# or `'error'`. A type of `'error'` means the resource is invalid.
|
84
|
+
# `:message` contains the message string itself.
|
85
|
+
def perform_additional_validation(&block)
|
86
|
+
additional_validations << block
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private
|
90
|
+
def additional_validation_messages(resource, profile_url)
|
91
|
+
additional_validations
|
92
|
+
.flat_map { |step| step.call(resource, profile_url) }
|
93
|
+
.select { |message| message.is_a? Hash }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Filter out unwanted validation messages. Any messages for which the
|
97
|
+
# block evalutates to a truthy value will be excluded.
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# validator do
|
101
|
+
# exclude_message { |message| message.type == 'info' }
|
102
|
+
# end
|
103
|
+
# @yieldparam message [Inferno::Entities::Message]
|
104
|
+
def exclude_message(&block)
|
105
|
+
@exclude_message = block if block_given?
|
106
|
+
@exclude_message
|
107
|
+
end
|
108
|
+
|
109
|
+
# @see Inferno::DSL::FHIRResourceValidation#resource_is_valid?
|
110
|
+
def resource_is_valid?(resource, profile_url, runnable)
|
111
|
+
profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url
|
112
|
+
|
113
|
+
begin
|
114
|
+
response = call_validator(resource, profile_url)
|
115
|
+
rescue StandardError => e
|
116
|
+
# This could be a complete failure to connect (validator isn't running)
|
117
|
+
# or a timeout (validator took too long to respond).
|
118
|
+
runnable.add_message('error', e.message)
|
119
|
+
raise Inferno::Exceptions::ErrorInValidatorException, "Unable to connect to validator at #{url}."
|
120
|
+
end
|
121
|
+
outcome = operation_outcome_from_validator_response(response, runnable)
|
122
|
+
|
123
|
+
message_hashes = message_hashes_from_outcome(outcome, resource, profile_url)
|
124
|
+
|
125
|
+
message_hashes
|
126
|
+
.each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
|
127
|
+
|
128
|
+
unless response.status == 200
|
129
|
+
raise Inferno::Exceptions::ErrorInValidatorException,
|
130
|
+
'Error occurred in the validator. Review Messages tab or validator service logs for more information.'
|
131
|
+
end
|
132
|
+
|
133
|
+
message_hashes
|
134
|
+
.none? { |message_hash| message_hash[:type] == 'error' }
|
135
|
+
rescue Inferno::Exceptions::ErrorInValidatorException
|
136
|
+
raise
|
137
|
+
rescue StandardError => e
|
138
|
+
runnable.add_message('error', e.message)
|
139
|
+
raise Inferno::Exceptions::ErrorInValidatorException,
|
140
|
+
'Error occurred in the validator. Review Messages tab or validator service logs for more information.'
|
141
|
+
end
|
142
|
+
|
143
|
+
# @private
|
144
|
+
def filter_messages(message_hashes)
|
145
|
+
message_hashes.reject! { |message| exclude_message.call(Entities::Message.new(message)) } if exclude_message
|
146
|
+
end
|
147
|
+
|
148
|
+
# @private
|
149
|
+
def message_hashes_from_outcome(outcome, resource, profile_url)
|
150
|
+
message_hashes = outcome.issue&.map { |issue| message_hash_from_issue(issue, resource) } || []
|
151
|
+
|
152
|
+
message_hashes.concat(additional_validation_messages(resource, profile_url))
|
153
|
+
|
154
|
+
filter_messages(message_hashes)
|
155
|
+
|
156
|
+
message_hashes
|
157
|
+
end
|
158
|
+
|
159
|
+
# @private
|
160
|
+
def message_hash_from_issue(issue, resource)
|
161
|
+
{
|
162
|
+
type: issue_severity(issue),
|
163
|
+
message: issue_message(issue, resource)
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
# @private
|
168
|
+
def issue_severity(issue)
|
169
|
+
case issue.severity
|
170
|
+
when 'warning'
|
171
|
+
'warning'
|
172
|
+
when 'information'
|
173
|
+
'info'
|
174
|
+
else
|
175
|
+
'error'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# @private
|
180
|
+
def issue_message(issue, resource)
|
181
|
+
location = if issue.respond_to?(:expression)
|
182
|
+
issue.expression&.join(', ')
|
183
|
+
else
|
184
|
+
issue.location&.join(', ')
|
185
|
+
end
|
186
|
+
|
187
|
+
location_prefix = resource.id ? "#{resource.resourceType}/#{resource.id}" : resource.resourceType
|
188
|
+
|
189
|
+
"#{location_prefix}: #{location}: #{issue&.details&.text}"
|
190
|
+
end
|
191
|
+
|
192
|
+
# @private
|
193
|
+
def wrap_resource_for_hl7_wrapper(resource, profile_url)
|
194
|
+
wrapped_resource = {
|
195
|
+
cliContext: {
|
196
|
+
# TODO: these should be configurable as well
|
197
|
+
sv: '4.0.1',
|
198
|
+
# displayWarnings: true, # -display-issues-are-warnings
|
199
|
+
# txServer: nil, # -tx n/a
|
200
|
+
igs: @igs || [],
|
201
|
+
# NOTE: this profile must be part of a loaded IG,
|
202
|
+
# otherwise the response is an HTTP 500 with no content
|
203
|
+
profiles: [profile_url]
|
204
|
+
},
|
205
|
+
filesToValidate: [
|
206
|
+
{
|
207
|
+
fileName: "#{resource.resourceType}/#{resource.id}.json",
|
208
|
+
fileContent: resource.to_json,
|
209
|
+
fileType: 'json'
|
210
|
+
}
|
211
|
+
],
|
212
|
+
sessionId: @session_id
|
213
|
+
}
|
214
|
+
wrapped_resource.to_json
|
215
|
+
end
|
216
|
+
|
217
|
+
# Post a resource to the validation service for validating.
|
218
|
+
#
|
219
|
+
# @param resource [FHIR::Model]
|
220
|
+
# @param profile_url [String]
|
221
|
+
# @return [String] the body of the validation response
|
222
|
+
def validate(resource, profile_url)
|
223
|
+
call_validator(resource, profile_url).body
|
224
|
+
end
|
225
|
+
|
226
|
+
# @private
|
227
|
+
def call_validator(resource, profile_url)
|
228
|
+
request_body = wrap_resource_for_hl7_wrapper(resource, profile_url)
|
229
|
+
Faraday.new(
|
230
|
+
url,
|
231
|
+
request: { timeout: 600 }
|
232
|
+
).post('validate', request_body, content_type: 'application/json')
|
233
|
+
end
|
234
|
+
|
235
|
+
# @private
|
236
|
+
def operation_outcome_from_hl7_wrapped_response(response)
|
237
|
+
res = JSON.parse(response)
|
238
|
+
|
239
|
+
@session_id = res['sessionId']
|
240
|
+
|
241
|
+
# assume for now that one resource -> one request
|
242
|
+
issues = res['outcomes'][0]['issues']&.map do |i|
|
243
|
+
{ severity: i['level'].downcase, expression: i['location'], details: { text: i['message'] } }
|
244
|
+
end
|
245
|
+
# this is circuitous, ideally we would map this response directly to message_hashes
|
246
|
+
FHIR::OperationOutcome.new(issue: issues)
|
247
|
+
end
|
248
|
+
|
249
|
+
# @private
|
250
|
+
def operation_outcome_from_validator_response(response, runnable)
|
251
|
+
if response.body.start_with? '{'
|
252
|
+
operation_outcome_from_hl7_wrapped_response(response.body)
|
253
|
+
else
|
254
|
+
runnable.add_message('error', "Validator Response: HTTP #{response.status}\n#{response.body}")
|
255
|
+
raise Inferno::Exceptions::ErrorInValidatorException,
|
256
|
+
'Validator response was an unexpected format. '\
|
257
|
+
'Review Messages tab or validator service logs for more information.'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
module ClassMethods
|
263
|
+
# @private
|
264
|
+
def fhir_validators
|
265
|
+
@fhir_validators ||= {}
|
266
|
+
end
|
267
|
+
|
268
|
+
# Define a validator
|
269
|
+
# @example
|
270
|
+
# fhir_resource_validator do
|
271
|
+
# url 'http://example.com/validator'
|
272
|
+
# exclude_message { |message| message.type == 'info' }
|
273
|
+
# perform_additional_validation do |resource, profile_url|
|
274
|
+
# if something_is_wrong
|
275
|
+
# { type: 'error', message: 'something is wrong' }
|
276
|
+
# else
|
277
|
+
# { type: 'info', message: 'everything is ok' }
|
278
|
+
# end
|
279
|
+
# end
|
280
|
+
# end
|
281
|
+
#
|
282
|
+
# @param name [Symbol] the name of the validator, only needed if you are
|
283
|
+
# using multiple validators
|
284
|
+
# @param required_suite_options [Hash] suite options that must be
|
285
|
+
# selected in order to use this validator
|
286
|
+
def fhir_resource_validator(name = :default, required_suite_options: nil, &block)
|
287
|
+
current_validators = fhir_validators[name] || []
|
288
|
+
|
289
|
+
new_validator = Inferno::DSL::FHIRResourceValidation::Validator.new(required_suite_options, &block)
|
290
|
+
|
291
|
+
current_validators.reject! { |validator| validator.requirements == required_suite_options }
|
292
|
+
current_validators << new_validator
|
293
|
+
|
294
|
+
fhir_validators[name] = current_validators
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -68,9 +68,10 @@ module Inferno
|
|
68
68
|
# @param name [Symbol] Name for this request to allow it to be used by
|
69
69
|
# other tests
|
70
70
|
# @param headers [Hash] Input headers here
|
71
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
71
72
|
# @return [Inferno::Entities::Request]
|
72
|
-
def get(url = '', client: :default, name: nil, headers: nil)
|
73
|
-
store_request('outgoing', name) do
|
73
|
+
def get(url = '', client: :default, name: nil, headers: nil, tags: [])
|
74
|
+
store_request('outgoing', name:, tags:) do
|
74
75
|
tcp_exception_handler do
|
75
76
|
client = http_client(client)
|
76
77
|
|
@@ -103,9 +104,10 @@ module Inferno
|
|
103
104
|
# @param name [Symbol] Name for this request to allow it to be used by
|
104
105
|
# other tests
|
105
106
|
# @param headers [Hash] Input headers here
|
107
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
106
108
|
# @return [Inferno::Entities::Request]
|
107
|
-
def post(url = '', body: nil, client: :default, name: nil, headers: nil)
|
108
|
-
store_request('outgoing', name) do
|
109
|
+
def post(url = '', body: nil, client: :default, name: nil, headers: nil, tags: [])
|
110
|
+
store_request('outgoing', name:, tags:) do
|
109
111
|
tcp_exception_handler do
|
110
112
|
client = http_client(client)
|
111
113
|
|
@@ -129,9 +131,10 @@ module Inferno
|
|
129
131
|
# @param name [Symbol] Name for this request to allow it to be used by
|
130
132
|
# other tests
|
131
133
|
# @param headers [Hash] Input headers here
|
134
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
132
135
|
# @return [Inferno::Entities::Request]
|
133
|
-
def delete(url = '', client: :default, name: :nil, headers: nil)
|
134
|
-
store_request('outgoing', name) do
|
136
|
+
def delete(url = '', client: :default, name: :nil, headers: nil, tags: [])
|
137
|
+
store_request('outgoing', name:, tags:) do
|
135
138
|
tcp_exception_handler do
|
136
139
|
client = http_client(client)
|
137
140
|
|
@@ -159,8 +162,9 @@ module Inferno
|
|
159
162
|
# @param name [Symbol] Name for this request to allow it to be used by
|
160
163
|
# other tests
|
161
164
|
# @param headers [Hash] Input headers here
|
165
|
+
# @param tags [Array<String>] a list of tags to assign to the request
|
162
166
|
# @return [Inferno::Entities::Request]
|
163
|
-
def stream(block, url = '', limit = 100, client: :default, name: nil, headers: nil)
|
167
|
+
def stream(block, url = '', limit = 100, client: :default, name: nil, headers: nil, tags: [])
|
164
168
|
streamed = []
|
165
169
|
|
166
170
|
collector = proc do |chunk, bytes|
|
@@ -169,7 +173,7 @@ module Inferno
|
|
169
173
|
block.call(chunk, bytes)
|
170
174
|
end
|
171
175
|
|
172
|
-
store_request('outgoing', name) do
|
176
|
+
store_request('outgoing', name:, tags:) do
|
173
177
|
tcp_exception_handler do
|
174
178
|
client = http_client(client)
|
175
179
|
|
@@ -36,24 +36,36 @@ module Inferno
|
|
36
36
|
request&.resource
|
37
37
|
end
|
38
38
|
|
39
|
+
# Returns requests which match all of the given tags
|
40
|
+
#
|
41
|
+
# @param tags [String]
|
42
|
+
# @return [Inferno::Entities::Request]
|
43
|
+
def load_tagged_requests(*tags)
|
44
|
+
return [] if tags.blank?
|
45
|
+
|
46
|
+
Repositories::Requests.new.tagged_requests(test_session_id, tags).tap do |tagged_requests|
|
47
|
+
requests.concat(tagged_requests)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
39
51
|
# @private
|
40
52
|
def named_request(name)
|
41
53
|
requests.find { |request| request.name == self.class.config.request_name(name.to_sym) }
|
42
54
|
end
|
43
55
|
|
44
56
|
# @private
|
45
|
-
def store_request(direction, name
|
57
|
+
def store_request(direction, name: nil, tags: [], &block)
|
46
58
|
response = block.call
|
47
59
|
|
48
60
|
name = self.class.config.request_name(name)
|
49
61
|
request =
|
50
62
|
if response.is_a? FHIR::ClientReply
|
51
63
|
Entities::Request.from_fhir_client_reply(
|
52
|
-
response, direction:, name:, test_session_id:
|
64
|
+
response, direction:, name:, test_session_id:, tags:
|
53
65
|
)
|
54
66
|
else
|
55
67
|
Entities::Request.from_http_response(
|
56
|
-
response, direction:, name:, test_session_id:
|
68
|
+
response, direction:, name:, test_session_id:, tags:
|
57
69
|
)
|
58
70
|
end
|
59
71
|
|