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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/cli/execute_script.rb +918 -0
  3. data/lib/inferno/apps/cli/main.rb +46 -0
  4. data/lib/inferno/apps/cli/session/cancel_run.rb +47 -0
  5. data/lib/inferno/apps/cli/session/connection.rb +47 -0
  6. data/lib/inferno/apps/cli/session/create_session.rb +159 -0
  7. data/lib/inferno/apps/cli/session/errors.rb +45 -0
  8. data/lib/inferno/apps/cli/session/session_compare.rb +391 -0
  9. data/lib/inferno/apps/cli/session/session_data.rb +39 -0
  10. data/lib/inferno/apps/cli/session/session_details.rb +27 -0
  11. data/lib/inferno/apps/cli/session/session_results.rb +39 -0
  12. data/lib/inferno/apps/cli/session/session_status.rb +69 -0
  13. data/lib/inferno/apps/cli/session/start_run.rb +245 -0
  14. data/lib/inferno/apps/cli/session_commands.rb +66 -0
  15. data/lib/inferno/apps/cli/templates/%library_name%.gemspec.tt +1 -1
  16. data/lib/inferno/apps/cli/templates/.gitignore +4 -0
  17. data/lib/inferno/apps/cli/templates/README.md.tt +14 -0
  18. data/lib/inferno/apps/cli/templates/Rakefile.tt +13 -0
  19. data/lib/inferno/apps/cli/templates/execution_scripts/%library_name%_script.yaml.tt +20 -0
  20. data/lib/inferno/apps/cli/templates/execution_scripts/%library_name%_script_expected.json.tt +244 -0
  21. data/lib/inferno/apps/cli/templates/execution_scripts/README.md.tt +16 -0
  22. data/lib/inferno/dsl/fhir_resource_navigation.rb +145 -27
  23. data/lib/inferno/dsl/must_support_assessment.rb +93 -23
  24. data/lib/inferno/dsl/must_support_metadata_extractor.rb +139 -21
  25. data/lib/inferno/dsl/resume_test_route.rb +4 -3
  26. data/lib/inferno/exceptions.rb +6 -0
  27. data/lib/inferno/repositories/test_sessions.rb +3 -0
  28. data/lib/inferno/utils/execution_script_runner.rb +90 -0
  29. data/lib/inferno/utils/preset_processor.rb +2 -0
  30. data/lib/inferno/version.rb +1 -1
  31. 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
@@ -20,3 +20,7 @@
20
20
  /docs/yard
21
21
  .yardoc
22
22
  node_modules
23
+
24
+ # execution script failed run artifacts
25
+ execution_scripts/**/*_actual_results*.json
26
+ execution_scripts/**/*_compared_results*.csv
@@ -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.