inferno_core 0.0.5 → 0.0.8.pre2
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/bin/inferno +7 -0
- data/lib/inferno/apps/cli/console.rb +12 -0
- data/lib/inferno/apps/cli/main.rb +18 -0
- data/lib/inferno/apps/cli/migration.rb +14 -0
- data/lib/inferno/apps/cli.rb +8 -0
- data/lib/inferno/apps/web/controllers/test_runs/create.rb +15 -2
- data/lib/inferno/apps/web/index.html.erb +1 -0
- data/lib/inferno/apps/web/serializers/hash_value_extractor.rb +11 -0
- data/lib/inferno/apps/web/serializers/test.rb +3 -4
- data/lib/inferno/apps/web/serializers/test_group.rb +4 -6
- data/lib/inferno/config/application.rb +4 -0
- data/lib/inferno/config/boot/db.rb +1 -9
- data/lib/inferno/config/boot/logging.rb +2 -0
- data/lib/inferno/config/boot/suites.rb +4 -6
- data/lib/inferno/db/migrations/001_create_initial_structure.rb +1 -1
- data/lib/inferno/db/schema.rb +1 -1
- data/lib/inferno/dsl/assertions.rb +85 -0
- data/lib/inferno/dsl/configurable.rb +126 -0
- data/lib/inferno/dsl/fhir_client.rb +22 -16
- data/lib/inferno/dsl/fhir_client_builder.rb +3 -3
- data/lib/inferno/dsl/fhir_validation.rb +105 -1
- data/lib/inferno/dsl/http_client.rb +14 -12
- data/lib/inferno/dsl/http_client_builder.rb +3 -3
- data/lib/inferno/dsl/request_storage.rb +26 -17
- data/lib/inferno/dsl/results.rb +1 -1
- data/lib/inferno/dsl/resume_test_route.rb +10 -10
- data/lib/inferno/dsl/runnable.rb +104 -38
- data/lib/inferno/entities/header.rb +14 -7
- data/lib/inferno/entities/message.rb +16 -8
- data/lib/inferno/entities/request.rb +34 -21
- data/lib/inferno/entities/result.rb +36 -29
- data/lib/inferno/entities/session_data.rb +12 -6
- data/lib/inferno/entities/test.rb +29 -2
- data/lib/inferno/entities/test_group.rb +8 -0
- data/lib/inferno/entities/test_run.rb +29 -6
- data/lib/inferno/entities/test_session.rb +16 -10
- data/lib/inferno/exceptions.rb +12 -0
- data/lib/inferno/public/217.bundle.js +1 -1
- data/lib/inferno/public/bundle.js +154 -1
- data/lib/inferno/public/bundle.js.LICENSE.txt +15 -0
- data/lib/inferno/repositories/in_memory_repository.rb +1 -1
- data/lib/inferno/repositories/results.rb +1 -1
- data/lib/inferno/repositories/test_runs.rb +15 -0
- data/lib/inferno/spec_support.rb +1 -1
- data/lib/inferno/test_runner.rb +21 -19
- data/lib/inferno/utils/markdown_formatter.rb +15 -0
- data/lib/inferno/utils/middleware/request_logger.rb +9 -3
- data/lib/inferno/utils/migration.rb +17 -0
- data/lib/inferno/version.rb +1 -1
- data/lib/inferno.rb +0 -4
- data/spec/factories/request.rb +14 -7
- metadata +46 -10
- data/bin/inferno-console +0 -8
@@ -1,38 +1,113 @@
|
|
1
1
|
module Inferno
|
2
2
|
module DSL
|
3
|
+
# This module contains the methods needed to configure a validator to
|
4
|
+
# perform validation of FHIR resources. The actual validation is performed
|
5
|
+
# by an external FHIR validation service. Tests will typically rely on
|
6
|
+
# `assert_valid_resource` for validation rather than directly calling
|
7
|
+
# methods on a validator.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# validator do
|
12
|
+
# url 'http://example.com/validator'
|
13
|
+
# exclude_message { |message| message.type == 'info' }
|
14
|
+
# perform_additional_validation do |resource, profile_url|
|
15
|
+
# if something_is_wrong
|
16
|
+
# { type: 'error', message: 'something is wrong' }
|
17
|
+
# else
|
18
|
+
# { type: 'info', message: 'everything is ok' }
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
3
22
|
module FHIRValidation
|
4
23
|
def self.included(klass)
|
5
24
|
klass.extend ClassMethods
|
6
25
|
end
|
7
26
|
|
27
|
+
# Perform validation, and add validation messages to the runnable
|
28
|
+
#
|
29
|
+
# @param resource [FHIR::Model]
|
30
|
+
# @param profile_url [String]
|
31
|
+
# @param validator [Symbol] the name of the validator to use
|
32
|
+
# @return [Boolean] whether the resource is valid
|
8
33
|
def resource_is_valid?(resource: self.resource, profile_url: nil, validator: :default)
|
9
34
|
find_validator(validator).resource_is_valid?(resource, profile_url, self)
|
10
35
|
end
|
11
36
|
|
37
|
+
# Find a particular validator. Looks through a runnable's parents up to
|
38
|
+
# the suite to find a validator with a particular name
|
12
39
|
def find_validator(validator_name)
|
13
40
|
self.class.find_validator(validator_name)
|
14
41
|
end
|
15
42
|
|
16
43
|
class Validator
|
44
|
+
# @private
|
17
45
|
def initialize(&block)
|
18
46
|
instance_eval(&block)
|
19
47
|
end
|
20
48
|
|
49
|
+
# @private
|
21
50
|
def default_validator_url
|
22
51
|
ENV.fetch('VALIDATOR_URL')
|
23
52
|
end
|
24
53
|
|
54
|
+
# Set the url of the validator service
|
55
|
+
#
|
56
|
+
# @param url [String]
|
25
57
|
def url(validator_url = nil)
|
26
58
|
@url = validator_url if validator_url
|
27
59
|
|
28
60
|
@url
|
29
61
|
end
|
30
62
|
|
63
|
+
# @private
|
64
|
+
def additional_validations
|
65
|
+
@additional_validations ||= []
|
66
|
+
end
|
67
|
+
|
68
|
+
# Perform validation steps in addition to FHIR validation.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# perform_additional_validation do |resource, profile_url|
|
72
|
+
# if something_is_wrong
|
73
|
+
# { type: 'error', message: 'something is wrong' }
|
74
|
+
# else
|
75
|
+
# { type: 'info', message: 'everything is ok' }
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
# @yieldparam resource [FHIR::Model] the resource being validated
|
79
|
+
# @yieldparam profile_url [String] the profile the resource is being
|
80
|
+
# validated against
|
81
|
+
# @yieldreturn [Array<Hash<Symbol, String>>,Hash<Symbol, String>] The
|
82
|
+
# block should return a Hash or an Array of Hashes if any validation
|
83
|
+
# messages should be added. The Hash must contain two keys: `:type`
|
84
|
+
# and `:message`. `:type` can have a value of `'info'`, `'warning'`,
|
85
|
+
# or `'error'`. A type of `'error'` means the resource is invalid.
|
86
|
+
# `:message` contains the message string itself.
|
87
|
+
def perform_additional_validation(&block)
|
88
|
+
additional_validations << block
|
89
|
+
end
|
90
|
+
|
91
|
+
# @private
|
92
|
+
def additional_validation_messages(resource, profile_url)
|
93
|
+
additional_validations
|
94
|
+
.flat_map { |step| step.call(resource, profile_url) }
|
95
|
+
.select { |message| message.is_a? Hash }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Filter out unwanted validation messages
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# validator do
|
102
|
+
# exclude_message { |message| message.type == 'info' }
|
103
|
+
# end
|
104
|
+
# @yieldparam message [Inferno::Entities::Message]
|
31
105
|
def exclude_message(&block)
|
32
106
|
@exclude_message = block if block_given?
|
33
107
|
@exclude_message
|
34
108
|
end
|
35
109
|
|
110
|
+
# @see Inferno::DSL::FHIRValidation#resource_is_valid?
|
36
111
|
def resource_is_valid?(resource, profile_url, runnable)
|
37
112
|
profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url
|
38
113
|
|
@@ -40,6 +115,8 @@ module Inferno
|
|
40
115
|
|
41
116
|
message_hashes = outcome.issue&.map { |issue| message_hash_from_issue(issue) } || []
|
42
117
|
|
118
|
+
message_hashes.concat(additional_validation_messages(resource, profile_url))
|
119
|
+
|
43
120
|
filter_messages(message_hashes)
|
44
121
|
|
45
122
|
message_hashes
|
@@ -47,10 +124,12 @@ module Inferno
|
|
47
124
|
.none? { |message_hash| message_hash[:type] == 'error' }
|
48
125
|
end
|
49
126
|
|
127
|
+
# @private
|
50
128
|
def filter_messages(message_hashes)
|
51
129
|
message_hashes.reject! { |message| exclude_message.call(Entities::Message.new(message)) } if exclude_message
|
52
130
|
end
|
53
131
|
|
132
|
+
# @private
|
54
133
|
def message_hash_from_issue(issue)
|
55
134
|
{
|
56
135
|
type: issue_severity(issue),
|
@@ -58,6 +137,7 @@ module Inferno
|
|
58
137
|
}
|
59
138
|
end
|
60
139
|
|
140
|
+
# @private
|
61
141
|
def issue_severity(issue)
|
62
142
|
case issue.severity
|
63
143
|
when 'warning'
|
@@ -69,6 +149,7 @@ module Inferno
|
|
69
149
|
end
|
70
150
|
end
|
71
151
|
|
152
|
+
# @private
|
72
153
|
def issue_message(issue)
|
73
154
|
location = if issue.respond_to?(:expression)
|
74
155
|
issue.expression&.join(', ')
|
@@ -79,6 +160,11 @@ module Inferno
|
|
79
160
|
"#{location}: #{issue&.details&.text}"
|
80
161
|
end
|
81
162
|
|
163
|
+
# Post a resource to the validation service for validating.
|
164
|
+
#
|
165
|
+
# @param resource [FHIR::Model]
|
166
|
+
# @param profile_url [String]
|
167
|
+
# @return [String] the body of the validation response
|
82
168
|
def validate(resource, profile_url)
|
83
169
|
RestClient.post(
|
84
170
|
"#{url}/validate",
|
@@ -89,15 +175,33 @@ module Inferno
|
|
89
175
|
end
|
90
176
|
|
91
177
|
module ClassMethods
|
92
|
-
# @
|
178
|
+
# @private
|
93
179
|
def fhir_validators
|
94
180
|
@fhir_validators ||= {}
|
95
181
|
end
|
96
182
|
|
183
|
+
# Define a validator
|
184
|
+
# @example
|
185
|
+
# validator do
|
186
|
+
# url 'http://example.com/validator'
|
187
|
+
# exclude_message { |message| message.type == 'info' }
|
188
|
+
# perform_additional_validation do |resource, profile_url|
|
189
|
+
# if something_is_wrong
|
190
|
+
# { type: 'error', message: 'something is wrong' }
|
191
|
+
# else
|
192
|
+
# { type: 'info', message: 'everything is ok' }
|
193
|
+
# end
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# @param name [Symbol] the name of the validator, only needed if you are
|
198
|
+
# using multiple validators
|
97
199
|
def validator(name = :default, &block)
|
98
200
|
fhir_validators[name] = Inferno::DSL::FHIRValidation::Validator.new(&block)
|
99
201
|
end
|
100
202
|
|
203
|
+
# Find a particular validator. Looks through a runnable's parents up to
|
204
|
+
# the suite to find a validator with a particular name
|
101
205
|
def find_validator(validator_name)
|
102
206
|
validator = fhir_validators[validator_name] || parent&.find_validator(validator_name)
|
103
207
|
|
@@ -16,7 +16,7 @@ module Inferno
|
|
16
16
|
# # performs a GET to https://example.com
|
17
17
|
# get
|
18
18
|
# # performs a GET to https://example.com/abc
|
19
|
-
#
|
19
|
+
# get('abc')
|
20
20
|
#
|
21
21
|
# request # the most recent request
|
22
22
|
# response # the most recent response
|
@@ -27,7 +27,7 @@ module Inferno
|
|
27
27
|
# @see Inferno::FHIRClientBuilder Documentation for the client
|
28
28
|
# configuration DSL
|
29
29
|
module HTTPClient
|
30
|
-
# @
|
30
|
+
# @private
|
31
31
|
def self.included(klass)
|
32
32
|
klass.extend ClassMethods
|
33
33
|
klass.include RequestStorage
|
@@ -47,7 +47,7 @@ module Inferno
|
|
47
47
|
http_clients[client] = HTTPClientBuilder.new.build(self, definition)
|
48
48
|
end
|
49
49
|
|
50
|
-
# @
|
50
|
+
# @private
|
51
51
|
def http_clients
|
52
52
|
@http_clients ||= {}
|
53
53
|
end
|
@@ -60,16 +60,17 @@ module Inferno
|
|
60
60
|
# @param client [Symbol]
|
61
61
|
# @param name [Symbol] Name for this request to allow it to be used by
|
62
62
|
# other tests
|
63
|
-
# @
|
63
|
+
# @option options [Hash] Input headers here - headers are optional and
|
64
|
+
# must be entered as the last piece of input to this method
|
64
65
|
# @return [Inferno::Entities::Request]
|
65
|
-
def get(url = '', client: :default, name: nil, **
|
66
|
+
def get(url = '', client: :default, name: nil, **options)
|
66
67
|
store_request('outgoing', name) do
|
67
68
|
client = http_client(client)
|
68
69
|
|
69
70
|
if client
|
70
|
-
client.get(url)
|
71
|
+
client.get(url, nil, options[:headers])
|
71
72
|
elsif url.match?(%r{\Ahttps?://})
|
72
|
-
Faraday.get(url)
|
73
|
+
Faraday.get(url, nil, options[:headers])
|
73
74
|
else
|
74
75
|
raise StandardError, 'Must use an absolute url or define an HTTP client with a base url'
|
75
76
|
end
|
@@ -85,16 +86,17 @@ module Inferno
|
|
85
86
|
# @param client [Symbol]
|
86
87
|
# @param name [Symbol] Name for this request to allow it to be used by
|
87
88
|
# other tests
|
88
|
-
# @
|
89
|
+
# @option options [Hash] Input headers here - headers are optional and
|
90
|
+
# must be entered as the last piece of input to this method
|
89
91
|
# @return [Inferno::Entities::Request]
|
90
|
-
def post(url = '', body: nil, client: :default, name: nil, **
|
92
|
+
def post(url = '', body: nil, client: :default, name: nil, **options)
|
91
93
|
store_request('outgoing', name) do
|
92
94
|
client = http_client(client)
|
93
95
|
|
94
96
|
if client
|
95
|
-
client.post(url, body)
|
97
|
+
client.post(url, body, options[:headers])
|
96
98
|
elsif url.match?(%r{\Ahttps?://})
|
97
|
-
Faraday.post(url, body)
|
99
|
+
Faraday.post(url, body, options[:headers])
|
98
100
|
else
|
99
101
|
raise StandardError, 'Must use an absolute url or define an HTTP client with a base url'
|
100
102
|
end
|
@@ -102,7 +104,7 @@ module Inferno
|
|
102
104
|
end
|
103
105
|
|
104
106
|
module ClassMethods
|
105
|
-
# @
|
107
|
+
# @private
|
106
108
|
def http_client_definitions
|
107
109
|
@http_client_definitions ||= {}
|
108
110
|
end
|
@@ -4,7 +4,7 @@ module Inferno
|
|
4
4
|
class HTTPClientBuilder
|
5
5
|
attr_accessor :runnable
|
6
6
|
|
7
|
-
# @
|
7
|
+
# @private
|
8
8
|
def build(runnable, block)
|
9
9
|
self.runnable = runnable
|
10
10
|
instance_exec(self, &block)
|
@@ -38,14 +38,14 @@ module Inferno
|
|
38
38
|
@headers ||= headers
|
39
39
|
end
|
40
40
|
|
41
|
-
# @
|
41
|
+
# @private
|
42
42
|
def method_missing(name, *args, &block)
|
43
43
|
return runnable.call(name, *args, &block) if runnable.respond_to? name
|
44
44
|
|
45
45
|
super
|
46
46
|
end
|
47
47
|
|
48
|
-
# @
|
48
|
+
# @private
|
49
49
|
def respond_to_missing?(name)
|
50
50
|
runnable.respond_to?(name) || super
|
51
51
|
end
|
@@ -38,13 +38,14 @@ module Inferno
|
|
38
38
|
|
39
39
|
# TODO: do a check in the test runner
|
40
40
|
def named_request(name)
|
41
|
-
requests.find { |request| request.name == name.to_sym }
|
41
|
+
requests.find { |request| request.name == self.class.config.request_name(name.to_sym) }
|
42
42
|
end
|
43
43
|
|
44
|
-
# @
|
44
|
+
# @private
|
45
45
|
def store_request(direction, name = nil, &block)
|
46
46
|
response = block.call
|
47
47
|
|
48
|
+
name = self.class.config.request_name(name)
|
48
49
|
request =
|
49
50
|
if response.is_a? FHIR::ClientReply
|
50
51
|
Entities::Request.from_fhir_client_reply(
|
@@ -60,52 +61,60 @@ module Inferno
|
|
60
61
|
request
|
61
62
|
end
|
62
63
|
|
63
|
-
# @
|
64
|
+
# @private
|
64
65
|
def load_named_requests
|
65
66
|
requests_repo = Inferno::Repositories::Requests.new
|
66
67
|
self.class.named_requests_used.map do |request_name|
|
67
|
-
|
68
|
-
|
68
|
+
request_alias = self.class.config.request_name(request_name)
|
69
|
+
request = requests_repo.find_named_request(test_session_id, request_alias)
|
70
|
+
raise StandardError, "Unable to find '#{request_alias}' request" if request.nil?
|
69
71
|
|
70
72
|
requests << request
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
74
76
|
module ClassMethods
|
75
|
-
# @
|
77
|
+
# @private
|
76
78
|
def named_requests_made
|
77
79
|
@named_requests_made ||= []
|
78
80
|
end
|
79
81
|
|
80
|
-
# @
|
82
|
+
# @private
|
81
83
|
def named_requests_used
|
82
84
|
@named_requests_used ||= []
|
83
85
|
end
|
84
86
|
|
85
87
|
# Specify the named requests made by a test
|
86
88
|
#
|
87
|
-
# @param
|
88
|
-
def makes_request(*
|
89
|
-
named_requests_made.concat(
|
89
|
+
# @param identifiers [Symbol] one or more request identifiers
|
90
|
+
def makes_request(*identifiers)
|
91
|
+
named_requests_made.concat(identifiers).uniq!
|
92
|
+
identifiers.each do |identifier|
|
93
|
+
config.add_request(identifier)
|
94
|
+
end
|
90
95
|
end
|
91
96
|
|
92
97
|
# Specify the name for a request received by a test
|
93
98
|
#
|
94
|
-
# @param
|
95
|
-
def receives_request(
|
96
|
-
|
99
|
+
# @param identifier [Symbol]
|
100
|
+
def receives_request(identifier)
|
101
|
+
config.add_request(identifier)
|
102
|
+
@incoming_request_name = identifier
|
97
103
|
end
|
98
104
|
|
99
|
-
# @
|
105
|
+
# @private
|
100
106
|
def incoming_request_name
|
101
107
|
@incoming_request_name
|
102
108
|
end
|
103
109
|
|
104
110
|
# Specify the named requests used by a test
|
105
111
|
#
|
106
|
-
# @param
|
107
|
-
def uses_request(*
|
108
|
-
named_requests_used.concat(
|
112
|
+
# @param identifiers [Symbol] one or more request identifiers
|
113
|
+
def uses_request(*identifiers)
|
114
|
+
named_requests_used.concat(identifiers).uniq!
|
115
|
+
identifiers.each do |identifier|
|
116
|
+
config.add_request(identifier)
|
117
|
+
end
|
109
118
|
end
|
110
119
|
end
|
111
120
|
end
|
data/lib/inferno/dsl/results.rb
CHANGED
@@ -4,7 +4,7 @@ module Inferno
|
|
4
4
|
module DSL
|
5
5
|
# A base class for creating routes to resume test execution upon receiving
|
6
6
|
# an incoming request.
|
7
|
-
# @
|
7
|
+
# @private
|
8
8
|
# @see Inferno::DSL::Runnable#resume_test_route
|
9
9
|
class ResumeTestRoute
|
10
10
|
include Hanami::Action
|
@@ -26,49 +26,49 @@ module Inferno
|
|
26
26
|
@request ||= Inferno::Entities::Request.from_rack_env(@params.env)
|
27
27
|
end
|
28
28
|
|
29
|
-
# @
|
29
|
+
# @private
|
30
30
|
def test_run
|
31
31
|
@test_run ||=
|
32
32
|
test_runs_repo.find_latest_waiting_by_identifier(test_run_identifier)
|
33
33
|
end
|
34
34
|
|
35
|
-
# @
|
35
|
+
# @private
|
36
36
|
def waiting_result
|
37
37
|
@waiting_result ||= results_repo.find_waiting_result(test_run_id: test_run.id)
|
38
38
|
end
|
39
39
|
|
40
|
-
# @
|
40
|
+
# @private
|
41
41
|
def update_result
|
42
42
|
results_repo.pass_waiting_result(waiting_result.id)
|
43
43
|
end
|
44
44
|
|
45
|
-
# @
|
45
|
+
# @private
|
46
46
|
def persist_request
|
47
47
|
requests_repo.create(
|
48
48
|
request.to_hash.merge(
|
49
49
|
test_session_id: test_run.test_session_id,
|
50
50
|
result_id: waiting_result.id,
|
51
|
-
name: test.incoming_request_name
|
51
|
+
name: test.config.request_name(test.incoming_request_name)
|
52
52
|
)
|
53
53
|
)
|
54
54
|
end
|
55
55
|
|
56
|
-
# @
|
56
|
+
# @private
|
57
57
|
def redirect_route
|
58
58
|
"/test_sessions/#{test_run.test_session_id}##{waiting_group_id}"
|
59
59
|
end
|
60
60
|
|
61
|
-
# @
|
61
|
+
# @private
|
62
62
|
def test
|
63
63
|
@test ||= tests_repo.find(waiting_result.test_id)
|
64
64
|
end
|
65
65
|
|
66
|
-
# @
|
66
|
+
# @private
|
67
67
|
def waiting_group_id
|
68
68
|
test.parent.id
|
69
69
|
end
|
70
70
|
|
71
|
-
# @
|
71
|
+
# @private
|
72
72
|
def call(_params)
|
73
73
|
if test_run.nil?
|
74
74
|
status(500, "Unable to find test run with identifier '#{test_run_identifier}'.")
|