inferno_core 0.4.22 → 0.4.23
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 +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 +1 -1
- 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: abed552b23b62bfad7ded41bdc4c417c15c8c91b13bd1ed48f462fda0de548db
|
4
|
+
data.tar.gz: 2600f94107f338fe9477780e8475482cef12b2b0ce374961bc9f506d8d0cef40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44cff660ebcec30498385684b088f157f447eba86d22baa684d1320a09ea7356be735d9c74ecf9df15d5ebc65a0f7874ed46139d95950aecdc000a6d6290a621
|
7
|
+
data.tar.gz: 378b6716dbd35c00731617a19bd854234a997c219718bba67ef9ef473a8cc86517e0a19c6d97fabeb20f7df71f1d4ecd149d8fab183969f5d0866e6b36d4648b
|
@@ -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
|
|