inferno_core 1.1.2 → 1.2.1
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/execute_script.rb +918 -0
- data/lib/inferno/apps/cli/main.rb +46 -0
- data/lib/inferno/apps/cli/session/cancel_run.rb +47 -0
- data/lib/inferno/apps/cli/session/connection.rb +47 -0
- data/lib/inferno/apps/cli/session/create_session.rb +159 -0
- data/lib/inferno/apps/cli/session/errors.rb +45 -0
- data/lib/inferno/apps/cli/session/session_compare.rb +391 -0
- data/lib/inferno/apps/cli/session/session_data.rb +39 -0
- data/lib/inferno/apps/cli/session/session_details.rb +27 -0
- data/lib/inferno/apps/cli/session/session_results.rb +39 -0
- data/lib/inferno/apps/cli/session/session_status.rb +69 -0
- data/lib/inferno/apps/cli/session/start_run.rb +245 -0
- data/lib/inferno/apps/cli/session_commands.rb +66 -0
- data/lib/inferno/apps/cli/templates/%library_name%.gemspec.tt +1 -1
- data/lib/inferno/apps/cli/templates/.gitignore +4 -0
- data/lib/inferno/apps/cli/templates/README.md.tt +14 -0
- data/lib/inferno/apps/cli/templates/Rakefile.tt +13 -0
- data/lib/inferno/apps/cli/templates/execution_scripts/%library_name%_script.yaml.tt +20 -0
- data/lib/inferno/apps/cli/templates/execution_scripts/%library_name%_script_expected.json.tt +244 -0
- data/lib/inferno/apps/cli/templates/execution_scripts/README.md.tt +16 -0
- data/lib/inferno/dsl/fhir_resource_navigation.rb +145 -27
- data/lib/inferno/dsl/must_support_assessment.rb +93 -23
- data/lib/inferno/dsl/must_support_metadata_extractor.rb +139 -21
- data/lib/inferno/dsl/resume_test_route.rb +4 -3
- data/lib/inferno/exceptions.rb +6 -0
- data/lib/inferno/repositories/test_sessions.rb +3 -0
- data/lib/inferno/utils/execution_script_runner.rb +90 -0
- data/lib/inferno/utils/preset_processor.rb +2 -0
- data/lib/inferno/version.rb +1 -1
- metadata +18 -2
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require_relative 'connection'
|
|
3
|
+
require_relative 'errors'
|
|
4
|
+
require_relative 'session_data'
|
|
5
|
+
require_relative 'session_details'
|
|
6
|
+
|
|
7
|
+
module Inferno
|
|
8
|
+
module CLI
|
|
9
|
+
module Session
|
|
10
|
+
class StartRun
|
|
11
|
+
include Connection
|
|
12
|
+
include Errors
|
|
13
|
+
|
|
14
|
+
COMMAND_OPTIONS = {
|
|
15
|
+
inputs: {
|
|
16
|
+
aliases: ['-i'],
|
|
17
|
+
type: :hash,
|
|
18
|
+
desc: 'Inputs (i.e: --inputs=foo:bar goo:baz); will merge and override current session inputs ' \
|
|
19
|
+
'(from preset or previous runs)'
|
|
20
|
+
}
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
attr_accessor :session_id, :options
|
|
24
|
+
|
|
25
|
+
def initialize(session_id, options)
|
|
26
|
+
self.session_id = session_id
|
|
27
|
+
self.options = options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run
|
|
31
|
+
puts JSON.pretty_generate(start_run)
|
|
32
|
+
exit(0)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def start_run
|
|
36
|
+
request_body = {
|
|
37
|
+
test_session_id: session_id,
|
|
38
|
+
"#{target_runnable_key}": target_runnable_id,
|
|
39
|
+
inputs: runnable_inputs
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
response = post('api/test_runs', request_body.to_json, content_type: 'application/json')
|
|
43
|
+
|
|
44
|
+
handle_web_api_error(response, :start_run) if response.status != 200
|
|
45
|
+
|
|
46
|
+
JSON.parse(response.body)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def session_details
|
|
50
|
+
@session_details ||= SessionDetails.new(session_id, options).details_for_session
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def target_runnable_details
|
|
54
|
+
@target_runnable_details ||= find_target_runnable
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def target_runnable_key
|
|
58
|
+
if target_runnable_details.key?('suite_summary')
|
|
59
|
+
'test_suite_id'
|
|
60
|
+
elsif target_runnable_details.key?('run_as_group')
|
|
61
|
+
'test_group_id'
|
|
62
|
+
else
|
|
63
|
+
'test_id'
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def target_runnable_id
|
|
68
|
+
target_runnable_details['id']
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def session_inputs
|
|
72
|
+
@session_inputs ||= SessionData.new(session_id, options).data_for_session(session_id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def user_inputs
|
|
76
|
+
@user_inputs ||= normalize_inputs(options[:inputs] || {})
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def normalize_inputs(inputs)
|
|
80
|
+
inputs.transform_values do |value|
|
|
81
|
+
next value.to_json if value.is_a?(Array) || value.is_a?(Hash)
|
|
82
|
+
next value unless value.to_s.start_with?('@')
|
|
83
|
+
|
|
84
|
+
path = File.expand_path(value[1..])
|
|
85
|
+
unless File.exist?(path)
|
|
86
|
+
puts JSON.pretty_generate({ errors: "File input not found: #{path}" })
|
|
87
|
+
exit(3)
|
|
88
|
+
end
|
|
89
|
+
File.read(path)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def runnable_inputs
|
|
94
|
+
@runnable_inputs ||= calculate_inputs
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# runnable_to_find can be a complete internal id, an internal id suffix, or short id displayed in the UI.
|
|
98
|
+
# Use 'suite' to run the whole suite.
|
|
99
|
+
def find_target_runnable
|
|
100
|
+
if options[:runnable].blank?
|
|
101
|
+
error_object = { errors: 'No runnable specified. Use a group/test id or "suite" to run the whole suite.' }
|
|
102
|
+
puts error_object.to_json
|
|
103
|
+
exit(3)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
target_runnable = options[:runnable] == 'suite' ? session_details['test_suite_id'] : options[:runnable]
|
|
107
|
+
target_runnable = target_runnable.to_s unless target_runnable.is_a?(String)
|
|
108
|
+
|
|
109
|
+
candidates = []
|
|
110
|
+
runnable_search(session_details['test_suite'], target_runnable, candidates)
|
|
111
|
+
if candidates.blank?
|
|
112
|
+
error_object =
|
|
113
|
+
{ errors: "Runnable '#{target_runnable}' not found in suite '#{session_details['test_suite_id']}'" }
|
|
114
|
+
puts error_object.to_json
|
|
115
|
+
exit(3)
|
|
116
|
+
elsif candidates.size > 1
|
|
117
|
+
error_object =
|
|
118
|
+
{ errors: "Runnable '#{target_runnable}' not unique in suite '#{session_details['test_suite_id']}'" }
|
|
119
|
+
puts error_object.to_json
|
|
120
|
+
exit(3)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
candidates.first
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def runnable_search(runnable_details, runnable_to_find, matches)
|
|
127
|
+
matches << runnable_details if runnable_matches?(runnable_details, runnable_to_find)
|
|
128
|
+
runnable_details['test_groups']&.each { |group| runnable_search(group, runnable_to_find, matches) }
|
|
129
|
+
runnable_details['tests']&.each { |test| runnable_search(test, runnable_to_find, matches) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def runnable_matches?(runnable_details, runnable_to_find)
|
|
133
|
+
runnable_details['id'] == runnable_to_find ||
|
|
134
|
+
runnable_details['id']&.ends_with?("-#{runnable_to_find}") ||
|
|
135
|
+
runnable_details['short_id'] == runnable_to_find
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# trying to replicate the process used in the UI
|
|
139
|
+
def calculate_inputs
|
|
140
|
+
target_runnable_details['inputs'].map do |runnable_input|
|
|
141
|
+
input_run_value(runnable_input)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def input_run_value(runnable_input)
|
|
146
|
+
input_name = runnable_input['name']
|
|
147
|
+
value = session_value_for_input(session_inputs, input_name)
|
|
148
|
+
value = user_inputs[input_name] if user_inputs[input_name].present?
|
|
149
|
+
value = runnable_input['default'] if value == '' && runnable_input['default'].present?
|
|
150
|
+
if runnable_input['type'] == 'auth_info'
|
|
151
|
+
component_object = value == '' ? {} : JSON.parse(value)
|
|
152
|
+
add_auth_info_component_defaults(component_object, runnable_input)
|
|
153
|
+
value = component_object.to_json
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
{ 'name' => input_name, 'value' => value }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def session_value_for_input(session_inputs, input_name)
|
|
160
|
+
session_input = session_inputs.find { |input| input['name'] == input_name }
|
|
161
|
+
session_input&.dig('value') || ''
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def add_auth_info_component_defaults(component_object, runnable_input)
|
|
165
|
+
default_from_runnable_components(component_object, runnable_input)
|
|
166
|
+
default_component_from_definitions(component_object, auth_info_mode_from_runnable_input(runnable_input))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def auth_info_mode_from_runnable_input(runnable_input)
|
|
170
|
+
mode = runnable_input.dig('options', 'mode')
|
|
171
|
+
if mode.nil?
|
|
172
|
+
'access'
|
|
173
|
+
elsif ['access', 'auth'].include?(mode)
|
|
174
|
+
mode
|
|
175
|
+
else
|
|
176
|
+
puts JSON.pretty_generate({ errors: "Failed to create run: unknown auth_info mode '#{mode}'." })
|
|
177
|
+
exit(3)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def default_component_from_definitions(component_object, mode)
|
|
182
|
+
component_object['auth_type'] = 'public' if component_object['auth_type'].blank?
|
|
183
|
+
auth_type = component_object['auth_type']
|
|
184
|
+
components_to_default = components_to_default(mode)
|
|
185
|
+
|
|
186
|
+
components_to_default.each do |component|
|
|
187
|
+
default_value = default_from_auth_info_component_definition(component, mode, auth_type)
|
|
188
|
+
if (component_object[component].blank? || component_object[component] == '') && !default_value.blank?
|
|
189
|
+
component_object[component] = default_value
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def default_from_runnable_components(component_object, runnable_input)
|
|
195
|
+
runnable_input.dig('options', 'components')&.each do |component|
|
|
196
|
+
component_name = component['name']
|
|
197
|
+
unless component_object.key?(component_name) || component['default'].blank?
|
|
198
|
+
component_object[component_name] = component['default']
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
AUTH_INFO_COMPONENT_AUTH_MODE_DEFAULTS = {
|
|
204
|
+
'use_discovery' => 'true',
|
|
205
|
+
'pkce_support' => 'enabled',
|
|
206
|
+
'pkce_code_challenge_method' => 'S256',
|
|
207
|
+
'auth_request_method' => 'GET'
|
|
208
|
+
}.freeze
|
|
209
|
+
|
|
210
|
+
AUTH_INFO_COMPONENT_ACCESS_MODE_DEFAULTS = {
|
|
211
|
+
'access_token' => '',
|
|
212
|
+
'refresh_token' => '',
|
|
213
|
+
'issue_time' => '',
|
|
214
|
+
'expires_in' => ''
|
|
215
|
+
}.freeze
|
|
216
|
+
|
|
217
|
+
def components_to_default(mode)
|
|
218
|
+
components_to_default =
|
|
219
|
+
case mode
|
|
220
|
+
when 'access'
|
|
221
|
+
AUTH_INFO_COMPONENT_ACCESS_MODE_DEFAULTS.keys
|
|
222
|
+
when 'auth'
|
|
223
|
+
AUTH_INFO_COMPONENT_AUTH_MODE_DEFAULTS.keys
|
|
224
|
+
end
|
|
225
|
+
components_to_default << 'encryption_algorithm'
|
|
226
|
+
|
|
227
|
+
components_to_default
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def default_from_auth_info_component_definition(component, mode, auth_type)
|
|
231
|
+
return 'public' if component == 'auth_type'
|
|
232
|
+
return 'ES384' if component == 'encryption_algorithm' && ['backend_services',
|
|
233
|
+
'asymmetric'].include?(auth_type)
|
|
234
|
+
|
|
235
|
+
case mode
|
|
236
|
+
when 'auth'
|
|
237
|
+
AUTH_INFO_COMPONENT_AUTH_MODE_DEFAULTS[component]
|
|
238
|
+
when 'access'
|
|
239
|
+
AUTH_INFO_COMPONENT_ACCESS_MODE_DEFAULTS[component]
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require_relative 'session/cancel_run'
|
|
2
|
+
require_relative 'session/create_session'
|
|
3
|
+
require_relative 'session/start_run'
|
|
4
|
+
require_relative 'session/session_status'
|
|
5
|
+
require_relative 'session/session_results'
|
|
6
|
+
require_relative 'session/session_data'
|
|
7
|
+
require_relative 'session/session_compare'
|
|
8
|
+
|
|
9
|
+
module Inferno
|
|
10
|
+
module CLI
|
|
11
|
+
module Session
|
|
12
|
+
class SessionCommands < Thor
|
|
13
|
+
def initialize(args = [], local_options = {}, config = {})
|
|
14
|
+
super
|
|
15
|
+
return unless @options[:inferno_base_url]
|
|
16
|
+
|
|
17
|
+
@options = @options.merge(inferno_base_url: "#{@options[:inferno_base_url].delete_suffix('/')}/")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class_option :inferno_base_url,
|
|
21
|
+
aliases: ['-I'],
|
|
22
|
+
type: :string,
|
|
23
|
+
desc: 'URL of the target Inferno service.'
|
|
24
|
+
|
|
25
|
+
desc 'create SUITE', 'Create a new session for a suite (internal ID, title, or short title).'
|
|
26
|
+
CreateSession::COMMAND_OPTIONS.each { |name, opts| option name, **opts }
|
|
27
|
+
def create(suite_id)
|
|
28
|
+
CreateSession.new(suite_id, options).run
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc 'cancel_run SESSION_ID', 'Cancel the current in-progress run for a session.'
|
|
32
|
+
def cancel_run(session_id)
|
|
33
|
+
CancelRun.new(session_id, options).run
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc 'start_run SESSION_ID RUNNABLE_ID', 'Initiate a test run on a session.'
|
|
37
|
+
StartRun::COMMAND_OPTIONS.each { |name, opts| option name, **opts }
|
|
38
|
+
def start_run(session_id, runnable_id)
|
|
39
|
+
StartRun.new(session_id, options.merge(runnable: runnable_id)).run
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc 'status SESSION_ID', 'Get the current run status of a session.'
|
|
43
|
+
def status(session_id)
|
|
44
|
+
SessionStatus.new(session_id, options).run
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
desc 'data SESSION_ID', 'Get the current session data (inputs) for a session.'
|
|
48
|
+
def data(session_id)
|
|
49
|
+
SessionData.new(session_id, options).run
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc 'results SESSION_ID', 'Get the results for a session.'
|
|
53
|
+
def results(session_id)
|
|
54
|
+
SessionResults.new(session_id, options).run
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc 'compare SESSION_ID',
|
|
58
|
+
'Compare the results of a session to expected results (from file or another session).'
|
|
59
|
+
SessionCompare::COMMAND_OPTIONS.each { |name, opts| option name, **opts }
|
|
60
|
+
def compare(session_id)
|
|
61
|
+
SessionCompare.new(session_id, options).run
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.metadata['inferno_test_kit'] = 'true'
|
|
17
17
|
# spec.metadata['homepage_uri'] = spec.homepage
|
|
18
18
|
# spec.metadata['source_code_uri'] = 'TODO'
|
|
19
|
-
spec.files = `[ -d .git ] && git ls-files -z lib config/presets LICENSE`.split("\x0")
|
|
19
|
+
spec.files = `[ -d .git ] && git ls-files -z lib config/presets execution_scripts LICENSE`.split("\x0")
|
|
20
20
|
|
|
21
21
|
spec.require_paths = ['lib']
|
|
22
22
|
end
|
|
@@ -25,6 +25,20 @@ More information about what is included in this repository can be [found here](h
|
|
|
25
25
|
- [Ruby API documentation](https://inferno-framework.github.io/inferno-core/docs/)
|
|
26
26
|
- [JSON API documentation](https://inferno-framework.github.io/inferno-core/api-docs/)
|
|
27
27
|
|
|
28
|
+
## Verifying Test Kit Logic
|
|
29
|
+
|
|
30
|
+
This template test kit includes examples for two tools that can be used to verify Inferno test kit logic:
|
|
31
|
+
- Unit tests written in rspec: test kit code is verified in isolation from other components. Examples
|
|
32
|
+
of these can be found in the `spec` directory. Those examples and any others defined in that directory
|
|
33
|
+
will be executed by the ruby.yml workflow (`.github/workflows/ruby.yml`)
|
|
34
|
+
if this test kit is committed to a Github repository.
|
|
35
|
+
- Execution scripts: test kit code is verified against previous results in a deployed Inferno
|
|
36
|
+
environment including the associated services. See [CI/CD Usage](https://inferno-framework.github.io/docs/ci-cd-usage.html)
|
|
37
|
+
in the Inferno documentation for more details on creating execution scripts. Examples of
|
|
38
|
+
these can be found in the `execution_scripts` directory. Those examples and any other defined in that directory
|
|
39
|
+
will be executed by the run_inferno_execution_scripts.yml workflow (`.github/workflows/run_inferno_execution_scripts.yml`)
|
|
40
|
+
if this test kit is committed to a Github repository.
|
|
41
|
+
|
|
28
42
|
## Example Inferno Test Kits
|
|
29
43
|
|
|
30
44
|
A list of all Test Kits registered with the Inferno Team can be found on the [Test Kit Registry](https://inferno-framework.github.io/community/test-kits.html) page.
|
|
@@ -5,6 +5,19 @@ begin
|
|
|
5
5
|
rescue LoadError # rubocop:disable Lint/SuppressedException
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
namespace :execute_scripts do
|
|
9
|
+
desc 'Run all execution script YAML files against a local Inferno instance (already running). ' \
|
|
10
|
+
'Optional FILTER env var restricts by File.fnmatch pattern, e.g. FILTER="execution_scripts/demo/*". ' \
|
|
11
|
+
'Optional INFERNO_BASE_URL env var sets the target Inferno URL, e.g. INFERNO_BASE_URL="http://localhost:4567/"'
|
|
12
|
+
task :run_all do
|
|
13
|
+
require 'inferno/utils/execution_script_runner'
|
|
14
|
+
Inferno::Utils::ExecutionScriptRunner.run_all(
|
|
15
|
+
pattern: ENV.fetch('FILTER', 'execution_scripts/**/*.yaml'),
|
|
16
|
+
inferno_base_url: ENV.fetch('INFERNO_BASE_URL', nil)
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
8
21
|
namespace :db do
|
|
9
22
|
desc 'Apply changes to the database'
|
|
10
23
|
task :migrate do
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
sessions:
|
|
2
|
+
- suite: <%= test_suite_id %>
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
- state_description: Session Created
|
|
6
|
+
status: created
|
|
7
|
+
start_run:
|
|
8
|
+
runnable: suite
|
|
9
|
+
inputs:
|
|
10
|
+
url: https://inferno.healthit.gov/reference-server/r4
|
|
11
|
+
credentials:
|
|
12
|
+
access_token: SAMPLE_TOKEN
|
|
13
|
+
patient_id: 85
|
|
14
|
+
action_description: Run the entire suite
|
|
15
|
+
|
|
16
|
+
- state_description: Suite executed (The second (last) test in the patient (last) group has just finished)
|
|
17
|
+
status: done
|
|
18
|
+
last_completed: suite
|
|
19
|
+
action: END_SCRIPT
|
|
20
|
+
action_description: Complete the script
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "c7770e47-3e51-43fd-b8cd-a971959c006d",
|
|
4
|
+
"created_at": "2026-03-02T22:36:46.497-05:00",
|
|
5
|
+
"inputs": [
|
|
6
|
+
{
|
|
7
|
+
"name": "url",
|
|
8
|
+
"value": "https://inferno.healthit.gov/reference-server/r4",
|
|
9
|
+
"type": "text"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "credentials",
|
|
13
|
+
"value": "{\"auth_type\":\"public\",\"access_token\":\"SAMPLE_TOKEN\",\"issue_time\":\"2026-03-02T22:36:46-05:00\",\"name\":\"credentials\"}",
|
|
14
|
+
"type": "auth_info"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"optional": false,
|
|
18
|
+
"outputs": [],
|
|
19
|
+
"requests": [
|
|
20
|
+
{
|
|
21
|
+
"id": "7d67c8ca-76d2-4253-acb4-e1e5a0f90c1a",
|
|
22
|
+
"direction": "outgoing",
|
|
23
|
+
"index": 1,
|
|
24
|
+
"result_id": "c7770e47-3e51-43fd-b8cd-a971959c006d",
|
|
25
|
+
"status": 200,
|
|
26
|
+
"timestamp": "2026-03-02T22:36:46.498-05:00",
|
|
27
|
+
"url": "https://inferno.healthit.gov/reference-server/r4/metadata",
|
|
28
|
+
"verb": "get"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"result": "pass",
|
|
32
|
+
"test_id": "<%= test_suite_id %>-capability_statement-capability_statement_read",
|
|
33
|
+
"test_run_id": "d0c43855-184a-4dcd-976e-83d6f99a996c",
|
|
34
|
+
"test_session_id": "eVoeqbmbIJV",
|
|
35
|
+
"updated_at": "2026-03-02T22:36:46.497-05:00"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "6e3dd213-c012-4a3c-b6de-3d1ad0c5951d",
|
|
39
|
+
"created_at": "2026-03-02T22:36:46.508-05:00",
|
|
40
|
+
"inputs": [
|
|
41
|
+
{
|
|
42
|
+
"name": "url",
|
|
43
|
+
"label": "FHIR Server Base Url",
|
|
44
|
+
"description": null,
|
|
45
|
+
"value": "https://inferno.healthit.gov/reference-server/r4",
|
|
46
|
+
"type": "text"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "credentials",
|
|
50
|
+
"label": "OAuth Credentials",
|
|
51
|
+
"description": null,
|
|
52
|
+
"value": "{\"auth_type\":\"public\",\"access_token\":\"SAMPLE_TOKEN\",\"issue_time\":\"2026-03-02T22:36:46-05:00\",\"name\":\"credentials\"}",
|
|
53
|
+
"type": "auth_info"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"optional": false,
|
|
57
|
+
"outputs": [],
|
|
58
|
+
"requests": [],
|
|
59
|
+
"result": "pass",
|
|
60
|
+
"test_group_id": "<%= test_suite_id %>-capability_statement",
|
|
61
|
+
"test_run_id": "d0c43855-184a-4dcd-976e-83d6f99a996c",
|
|
62
|
+
"test_session_id": "eVoeqbmbIJV",
|
|
63
|
+
"updated_at": "2026-03-02T22:36:46.508-05:00"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "98887da7-6525-4545-bb12-865c376f1889",
|
|
67
|
+
"created_at": "2026-03-02T22:36:46.629-05:00",
|
|
68
|
+
"inputs": [
|
|
69
|
+
{
|
|
70
|
+
"name": "patient_id",
|
|
71
|
+
"value": "85",
|
|
72
|
+
"type": "text"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "url",
|
|
76
|
+
"value": "https://inferno.healthit.gov/reference-server/r4",
|
|
77
|
+
"type": "text"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "credentials",
|
|
81
|
+
"value": "{\"auth_type\":\"public\",\"access_token\":\"SAMPLE_TOKEN\",\"issue_time\":\"2026-03-02T22:36:46-05:00\",\"name\":\"credentials\"}",
|
|
82
|
+
"type": "auth_info"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"optional": false,
|
|
86
|
+
"outputs": [],
|
|
87
|
+
"requests": [
|
|
88
|
+
{
|
|
89
|
+
"id": "694ec151-aa8f-455b-82d7-9f7d2771cf09",
|
|
90
|
+
"direction": "outgoing",
|
|
91
|
+
"index": 2,
|
|
92
|
+
"result_id": "98887da7-6525-4545-bb12-865c376f1889",
|
|
93
|
+
"status": 200,
|
|
94
|
+
"timestamp": "2026-03-02T22:36:46.630-05:00",
|
|
95
|
+
"url": "https://inferno.healthit.gov/reference-server/r4/Patient/85",
|
|
96
|
+
"verb": "get"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"result": "pass",
|
|
100
|
+
"test_id": "<%= test_suite_id %>-patient_group-Test01",
|
|
101
|
+
"test_run_id": "d0c43855-184a-4dcd-976e-83d6f99a996c",
|
|
102
|
+
"test_session_id": "eVoeqbmbIJV",
|
|
103
|
+
"updated_at": "2026-03-02T22:36:46.629-05:00"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "8e822421-cef1-4889-99cf-b25212a66884",
|
|
107
|
+
"created_at": "2026-03-02T22:36:50.645-05:00",
|
|
108
|
+
"inputs": [
|
|
109
|
+
{
|
|
110
|
+
"name": "url",
|
|
111
|
+
"value": "https://inferno.healthit.gov/reference-server/r4",
|
|
112
|
+
"type": "text"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "credentials",
|
|
116
|
+
"value": "{\"auth_type\":\"public\",\"access_token\":\"SAMPLE_TOKEN\",\"issue_time\":\"2026-03-02T22:36:46-05:00\",\"name\":\"credentials\"}",
|
|
117
|
+
"type": "auth_info"
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"messages": [
|
|
121
|
+
{
|
|
122
|
+
"message": "Patient/85: Patient.meta.profile[0]: A definition could not be found for Canonical URL 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'",
|
|
123
|
+
"type": "info"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"message": "Patient/85: Patient.extension[0]: Unknown extension http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
|
|
127
|
+
"type": "info"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"message": "Patient/85: Patient.extension[0].extension[0].value.ofType(Coding): The definition for the Code System with URI 'urn:oid:2.16.840.1.113883.6.238' doesn't provide any codes so the code cannot be validated",
|
|
131
|
+
"type": "info"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"message": "Patient/85: Patient.extension[1]: Unknown extension http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity",
|
|
135
|
+
"type": "info"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"message": "Patient/85: Patient.extension[1].extension[0].value.ofType(Coding): The definition for the Code System with URI 'urn:oid:2.16.840.1.113883.6.238' doesn't provide any codes so the code cannot be validated",
|
|
139
|
+
"type": "info"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"message": "Patient/85: Patient.extension[2]: Unknown extension http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex",
|
|
143
|
+
"type": "info"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"message": "Patient/85: Patient.identifier[2].type: None of the codings provided are in the value set 'IdentifierType' (http://hl7.org/fhir/ValueSet/identifier-type|4.0.1), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://terminology.hl7.org/CodeSystem/v2-0203#SS)",
|
|
147
|
+
"type": "warning"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"message": "Patient/85: Patient.meta.profile[0]: Profile reference 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient' has not been checked because it could not be found, and the validator is set to not fetch unknown profiles",
|
|
151
|
+
"type": "warning"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"optional": false,
|
|
155
|
+
"outputs": [],
|
|
156
|
+
"requests": [
|
|
157
|
+
{
|
|
158
|
+
"id": "694ec151-aa8f-455b-82d7-9f7d2771cf09",
|
|
159
|
+
"direction": "outgoing",
|
|
160
|
+
"index": 2,
|
|
161
|
+
"result_id": "98887da7-6525-4545-bb12-865c376f1889",
|
|
162
|
+
"status": 200,
|
|
163
|
+
"timestamp": "2026-03-02T22:36:46.630-05:00",
|
|
164
|
+
"url": "https://inferno.healthit.gov/reference-server/r4/Patient/85",
|
|
165
|
+
"verb": "get"
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"result": "pass",
|
|
169
|
+
"test_id": "<%= test_suite_id %>-patient_group-Test02",
|
|
170
|
+
"test_run_id": "d0c43855-184a-4dcd-976e-83d6f99a996c",
|
|
171
|
+
"test_session_id": "eVoeqbmbIJV",
|
|
172
|
+
"updated_at": "2026-03-02T22:36:50.645-05:00"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "52466b2f-c11f-4697-9570-6fe88b552a1e",
|
|
176
|
+
"created_at": "2026-03-02T22:36:50.656-05:00",
|
|
177
|
+
"inputs": [
|
|
178
|
+
{
|
|
179
|
+
"name": "patient_id",
|
|
180
|
+
"label": "Patient ID",
|
|
181
|
+
"description": null,
|
|
182
|
+
"value": "85",
|
|
183
|
+
"type": "text"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "url",
|
|
187
|
+
"label": "FHIR Server Base Url",
|
|
188
|
+
"description": null,
|
|
189
|
+
"value": "https://inferno.healthit.gov/reference-server/r4",
|
|
190
|
+
"type": "text"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "credentials",
|
|
194
|
+
"label": "OAuth Credentials",
|
|
195
|
+
"description": null,
|
|
196
|
+
"value": "{\"auth_type\":\"public\",\"access_token\":\"SAMPLE_TOKEN\",\"issue_time\":\"2026-03-02T22:36:46-05:00\",\"name\":\"credentials\"}",
|
|
197
|
+
"type": "auth_info"
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
"optional": false,
|
|
201
|
+
"outputs": [],
|
|
202
|
+
"requests": [],
|
|
203
|
+
"result": "pass",
|
|
204
|
+
"test_group_id": "<%= test_suite_id %>-patient_group",
|
|
205
|
+
"test_run_id": "d0c43855-184a-4dcd-976e-83d6f99a996c",
|
|
206
|
+
"test_session_id": "eVoeqbmbIJV",
|
|
207
|
+
"updated_at": "2026-03-02T22:36:50.656-05:00"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "9d550f22-08de-4b39-b05a-93b69cf979e7",
|
|
211
|
+
"created_at": "2026-03-02T22:36:50.659-05:00",
|
|
212
|
+
"inputs": [
|
|
213
|
+
{
|
|
214
|
+
"name": "url",
|
|
215
|
+
"label": "FHIR Server Base Url",
|
|
216
|
+
"description": null,
|
|
217
|
+
"value": "https://inferno.healthit.gov/reference-server/r4",
|
|
218
|
+
"type": "text"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"name": "credentials",
|
|
222
|
+
"label": "OAuth Credentials",
|
|
223
|
+
"description": null,
|
|
224
|
+
"value": "{\"auth_type\":\"public\",\"access_token\":\"SAMPLE_TOKEN\",\"issue_time\":\"2026-03-02T22:36:46-05:00\",\"name\":\"credentials\"}",
|
|
225
|
+
"type": "auth_info"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "patient_id",
|
|
229
|
+
"label": "Patient ID",
|
|
230
|
+
"description": null,
|
|
231
|
+
"value": "85",
|
|
232
|
+
"type": "text"
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
"optional": false,
|
|
236
|
+
"outputs": [],
|
|
237
|
+
"requests": [],
|
|
238
|
+
"result": "pass",
|
|
239
|
+
"test_run_id": "d0c43855-184a-4dcd-976e-83d6f99a996c",
|
|
240
|
+
"test_session_id": "eVoeqbmbIJV",
|
|
241
|
+
"test_suite_id": "<%= test_suite_id %>",
|
|
242
|
+
"updated_at": "2026-03-02T22:36:50.659-05:00"
|
|
243
|
+
}
|
|
244
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Using Core Execution Scripts
|
|
2
|
+
|
|
3
|
+
This directory contains [execution scripts](https://inferno-framework.github.io/docs/advanced-test-features/scripting-execution.html)
|
|
4
|
+
that demonstrate and validate the behavior of suites defined
|
|
5
|
+
by this test kit in the context of running Inferno services
|
|
6
|
+
allowing for end-to-end executions that check for expected suite execution
|
|
7
|
+
results when using Inferno services like the FHIR Validator and FHIR Path
|
|
8
|
+
Service as well as dependency services that Inferno does not control such
|
|
9
|
+
as `tx.fhir.org`.
|
|
10
|
+
|
|
11
|
+
Execution scripts defined here will
|
|
12
|
+
be [executed](https://inferno-framework.github.io/docs/advanced-test-features/scripting-execution.html#execution)
|
|
13
|
+
automatically on pull requests as a part of the github
|
|
14
|
+
workflows and can also be executed locally using the
|
|
15
|
+
[`execute_script` CLI](https://inferno-framework.github.io/docs/getting-started/inferno-cli.html#complex-scripted-execution)
|
|
16
|
+
on individual scripts or the `execute_scripts:run_all` rake task.
|