inferno_core 0.6.9 → 0.6.10
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/evaluate/database.yml +15 -0
- data/lib/inferno/apps/cli/evaluate/docker-compose.evaluate.yml +16 -0
- data/lib/inferno/apps/cli/evaluate.rb +52 -4
- data/lib/inferno/apps/cli/main.rb +5 -1
- data/lib/inferno/apps/cli/requirements.rb +28 -0
- data/lib/inferno/apps/cli/requirements_exporter.rb +194 -0
- data/lib/inferno/apps/cli/suite.rb +21 -0
- data/lib/inferno/apps/cli/templates/lib/%library_name%/example_suite/patient_group.rb.tt +141 -0
- data/lib/inferno/apps/cli/templates/lib/%library_name%/example_suite.rb.tt +128 -0
- data/lib/inferno/apps/cli/templates/lib/%library_name%/metadata.rb.tt +65 -3
- data/lib/inferno/apps/cli/templates/lib/%library_name%/version.rb.tt +1 -0
- data/lib/inferno/apps/cli/templates/lib/%library_name%.rb.tt +1 -1
- data/lib/inferno/apps/web/serializers/input.rb +1 -0
- data/lib/inferno/apps/web/serializers/test.rb +1 -0
- data/lib/inferno/apps/web/serializers/test_group.rb +1 -0
- data/lib/inferno/apps/web/serializers/test_suite.rb +1 -0
- data/lib/inferno/config/boot/suites.rb +3 -0
- data/lib/inferno/dsl/fhir_evaluation/default.yml +68 -0
- data/lib/inferno/dsl/fhir_evaluation/evaluator.rb +3 -5
- data/lib/inferno/dsl/fhir_evaluation/rules/all_defined_extensions_have_examples.rb +2 -2
- data/lib/inferno/dsl/fhir_evaluation/rules/all_extensions_used.rb +2 -2
- data/lib/inferno/dsl/fhir_evaluation/rules/all_must_supports_present.rb +1 -1
- data/lib/inferno/dsl/fhir_evaluation/rules/all_profiles_have_examples.rb +1 -1
- data/lib/inferno/dsl/fhir_evaluation/rules/all_references_resolve.rb +2 -2
- data/lib/inferno/dsl/fhir_evaluation/rules/all_resources_reachable.rb +2 -2
- data/lib/inferno/dsl/fhir_evaluation/rules/all_search_parameters_have_examples.rb +22 -11
- data/lib/inferno/dsl/fhir_evaluation/rules/differential_content_has_examples.rb +2 -2
- data/lib/inferno/dsl/fhir_evaluation/rules/value_sets_demonstrate.rb +4 -4
- data/lib/inferno/dsl/fhir_resource_validation.rb +25 -3
- data/lib/inferno/dsl/fhirpath_evaluation.rb +25 -1
- data/lib/inferno/dsl/input_output_handling.rb +1 -0
- data/lib/inferno/dsl/runnable.rb +5 -0
- data/lib/inferno/dsl/short_id_manager.rb +55 -0
- data/lib/inferno/entities/input.rb +14 -5
- data/lib/inferno/entities/requirement.rb +15 -3
- data/lib/inferno/entities/test.rb +3 -1
- data/lib/inferno/entities/test_group.rb +3 -1
- data/lib/inferno/entities/test_suite.rb +2 -0
- data/lib/inferno/exceptions.rb +6 -0
- data/lib/inferno/public/237.bundle.js +1 -1
- data/lib/inferno/public/bundle.js +54 -54
- data/lib/inferno/public/bundle.js.LICENSE.txt +3 -36
- data/lib/inferno/repositories/requirements.rb +6 -2
- data/lib/inferno/version.rb +1 -1
- data/spec/shared/test_kit_examples.rb +32 -0
- metadata +24 -3
- data/lib/inferno/apps/cli/templates/lib/%library_name%/patient_group.rb.tt +0 -44
@@ -5,11 +5,73 @@ module <%= module_name %>
|
|
5
5
|
id :<%= test_kit_id %>
|
6
6
|
title '<%= title_name %>'
|
7
7
|
description <<~DESCRIPTION
|
8
|
-
|
8
|
+
The Example Test Kit is a testing tool for Health IT systems seeking to meet the requirements of API Criterion within the Example Certification Program.
|
9
|
+
|
10
|
+
*or*
|
11
|
+
|
12
|
+
The Example Test Kit validates the conformance of a server implementation to a specific version of the [Example IG](https://example.com/example).
|
13
|
+
Currently, Inferno can test against implementations of following versions of the Example IG: v1.0.0, v1.3.0, v2.0.0, and v3.0.1.
|
14
|
+
|
15
|
+
<!-- break -->
|
16
|
+
|
17
|
+
## Getting Started
|
18
|
+
|
19
|
+
Please select which approved version of each standard to use, and click ‘Create Test Session’ to begin testing.
|
20
|
+
|
21
|
+
This test kit includes a [simulated conformant FHIR API](https://inferno.healthit.gov/reference-server/)
|
22
|
+
that can be used to demonstrate success for all tests. This simulated API is open source and is available on GitHub.
|
23
|
+
Visit the [walkthrough](https://example.com/Walkthrough) for a demonstration
|
24
|
+
of using these tests against the provided simulated FHIR API.
|
25
|
+
|
26
|
+
## Status
|
27
|
+
|
28
|
+
The Example Test Kit is actively developed and updates are released monthly.
|
29
|
+
|
30
|
+
*or*
|
31
|
+
|
32
|
+
These tests are a **DRAFT**. Future versions of these tests may verify other requirements and may change how these requirements are tested.
|
33
|
+
|
34
|
+
## Conformance
|
35
|
+
|
36
|
+
The test kit currently tests all requirements for the
|
37
|
+
[API Criterion within the Example Certification Program](https://example.com/api-criterion).
|
38
|
+
This includes:
|
39
|
+
- The Lorum IG [v1.0.0](https://example.com/lorum/1.0.0)
|
40
|
+
- The Ipsum IG [v2.0.0](https://example.com/ipsum/2.0.0), [v3.0.1](https://example.com/ipsum/3.0.1)
|
41
|
+
- The Dolor IG [v2.0.2](https://example.com/dolor/2.0.2)
|
42
|
+
|
43
|
+
*or*
|
44
|
+
|
45
|
+
The test kit currently tests the following requirements:
|
46
|
+
- Vel mattis erat semper ut
|
47
|
+
- Suspendisse eget tempor
|
48
|
+
- Nulla eu cursus turpis
|
49
|
+
- Praesent orci diam
|
50
|
+
|
51
|
+
|
52
|
+
## Repository
|
53
|
+
|
54
|
+
The Example Kit can be
|
55
|
+
[downloaded from its GitHub repository](https://example.com/example-test-kit-repo),
|
56
|
+
where additional resources and documentation are also available to help users get
|
57
|
+
started with the testing process. The repository [Wiki](https://example.com/example-test-kit-repo/wiki/)
|
58
|
+
provides a [FAQ](https://example.com/example-test-kit-repo/wiki/FAQ) for testers,
|
59
|
+
and the [Releases](https://example.com/example-test-kit-repo/releases) page provides information about each new release.
|
60
|
+
|
61
|
+
## Providing Feedback and Reporting Issues
|
62
|
+
|
63
|
+
We welcome feedback on the tests, including but not limited to the following areas:
|
64
|
+
|
65
|
+
- Validation logic, such as potential bugs, lax checks, and unexpected failures.
|
66
|
+
- Requirements coverage, such as requirements that have been missed, tests that necessitate features that the IG does not require, or other issues with the interpretation of the IG’s requirements.
|
67
|
+
- User experience, such as confusing or missing information in the test UI.
|
68
|
+
|
69
|
+
Please report any issues with this set of tests in the [issues section](https://example.com/example-test-kit-repo/issues) of the repository.
|
9
70
|
DESCRIPTION
|
71
|
+
|
10
72
|
suite_ids [:<%= test_suite_id %>]
|
11
|
-
#
|
12
|
-
|
73
|
+
tags [] # E.g., ['SMART App Launch', 'US Core']
|
74
|
+
last_updated LAST_UPDATED
|
13
75
|
version VERSION
|
14
76
|
maturity 'Low'
|
15
77
|
authors <%= authors %>
|
@@ -1 +1 @@
|
|
1
|
-
require_relative '<%= library_name %>/
|
1
|
+
require_relative '<%= library_name %>/example_suite'
|
@@ -45,6 +45,9 @@ Inferno::Application.register_provider(:suites) do
|
|
45
45
|
if descendant.id.blank? || descendant.id == 'Inferno::Entities::TestSuite'
|
46
46
|
raise StandardError, "Error initializing test suite #{descendant.name}: test suite ID is not set"
|
47
47
|
end
|
48
|
+
|
49
|
+
# This will lock the short IDs if a short ID map for this suite is present
|
50
|
+
descendant.assign_short_ids
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
Environment:
|
2
|
+
ExternalValidator:
|
3
|
+
Enabled: false
|
4
|
+
Url: ''
|
5
|
+
VSAC:
|
6
|
+
Username: 'apikey'
|
7
|
+
Password: ''
|
8
|
+
|
9
|
+
Rule:
|
10
|
+
AllReferencesResolve:
|
11
|
+
Description: 'All References in Examples resolve to each other'
|
12
|
+
Enabled: true
|
13
|
+
AllResourcesReachable:
|
14
|
+
Description: 'All resources in Examples are reachable'
|
15
|
+
Enabled: true
|
16
|
+
AllMustSupportsPresent:
|
17
|
+
Description: 'An instance of all MustSupport elements, extensions, and slices is present in the given resources'
|
18
|
+
Enabled: true
|
19
|
+
# RequirementExtensionUrl accepts an extension URL which tags element definitions as required for the purposes of this test, even if not Must Support.
|
20
|
+
# For instance, US Core elements with the "http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement" extension
|
21
|
+
# may be considered required even if not necessarily tagged Must Support.
|
22
|
+
# An instance of the extension must have valueBoolean=true to be recognized.
|
23
|
+
RequirementExtensionUrl: null
|
24
|
+
|
25
|
+
# Set WriteMetadataForDebugging:true to have the test write out the metadata it used to a temporary file
|
26
|
+
WriteMetadataForDebugging: false
|
27
|
+
|
28
|
+
ConformanceOptions:
|
29
|
+
# ConformanceOptions allows selecting from a few approaches to determine which subset of resources
|
30
|
+
# should be used to search for the MustSupport elements from each profile.
|
31
|
+
# Resources that are not the same type as the target of the profile are never searched, regardless of option.
|
32
|
+
|
33
|
+
# - If considerMetaProfile, the search will include resources that declare the current profile in meta.profile
|
34
|
+
considerMetaProfile: true
|
35
|
+
|
36
|
+
# - If considerValidationResults, resources will be validated against each profile to determine which they should be checked against.
|
37
|
+
# The search will include resources that validate against the current profile
|
38
|
+
# (in other words, resources for which a validation request produces no errors).
|
39
|
+
considerValidationResults: false
|
40
|
+
|
41
|
+
# - If considerOnlyResourceType, the search will include resources of the same type as the profile target type (StructureDefintion.type)
|
42
|
+
considerOnlyResourceType: false
|
43
|
+
AllExtensionsUsed:
|
44
|
+
Description: 'All extensions specified in profiles are represented in Examples'
|
45
|
+
Enabled: true
|
46
|
+
DifferentialContentHasExamples:
|
47
|
+
Enabled: true
|
48
|
+
ValueSetsDemonstrate:
|
49
|
+
Descripton: 'Examples demonstrate reasonable coverage of valuesets defines in an IG. (Note this probably only makes sense for small valuesets such as status options, not something like disease codes from SNOMED)'
|
50
|
+
Enabled: true
|
51
|
+
IgnoreUnloadableValueset: false
|
52
|
+
Exclude:
|
53
|
+
URN: true # Exclude if system is provided as Uniform Resource Name "urn:"
|
54
|
+
Filter: true # Exclude filter
|
55
|
+
SystemOnly: true # Exclude if only system is provided (e.g. http://loing.org)
|
56
|
+
AllSearchParametersHaveExamples:
|
57
|
+
Description: 'Examples cover all search parameters defined in an IG'
|
58
|
+
Enabled: true
|
59
|
+
AllDefinedExtensionsHaveExamples:
|
60
|
+
Description: 'All defined extensions are represented in Examples'
|
61
|
+
Enabled: true
|
62
|
+
AllProfilesHaveExamples:
|
63
|
+
Description: 'All profiles defined in an IG have at least one example instance'
|
64
|
+
Enabled: true
|
65
|
+
ConformanceOptions:
|
66
|
+
considerMetaProfile: true
|
67
|
+
considerValidationResults: false
|
68
|
+
considerOnlyResourceType: false
|
@@ -24,13 +24,11 @@ module Inferno
|
|
24
24
|
def evaluate(data, config = Config.new)
|
25
25
|
context = EvaluationContext.new(@ig, data, config, validator)
|
26
26
|
|
27
|
-
active_rules = []
|
28
27
|
config.data['Rule'].each do |rulename, rule_details|
|
29
|
-
|
30
|
-
end
|
28
|
+
next unless rule_details['Enabled']
|
31
29
|
|
32
|
-
|
33
|
-
|
30
|
+
Rule.descendants.select { |rule| rule.name.demodulize == rulename }
|
31
|
+
.each { |rule| rule.new.check(context) }
|
34
32
|
end
|
35
33
|
|
36
34
|
context.results
|
@@ -25,10 +25,10 @@ module Inferno
|
|
25
25
|
end
|
26
26
|
|
27
27
|
if unused_extensions.any?
|
28
|
-
message = "Found
|
28
|
+
message = "Found defined extensions in the IG without examples: \n\t #{unused_extensions.join(', ')}"
|
29
29
|
result = EvaluationResult.new(message, rule: self)
|
30
30
|
else
|
31
|
-
message = 'All defined extensions
|
31
|
+
message = 'All defined extensions in the IG have examples.'
|
32
32
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
33
33
|
end
|
34
34
|
|
@@ -12,7 +12,7 @@ module Inferno
|
|
12
12
|
message = get_fail_message(unused_extensions)
|
13
13
|
result = EvaluationResult.new(message, rule: self)
|
14
14
|
else
|
15
|
-
message = 'All extensions specified in profiles are
|
15
|
+
message = 'All extensions specified in profiles are used in examples.'
|
16
16
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
17
17
|
end
|
18
18
|
|
@@ -63,7 +63,7 @@ module Inferno
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def get_fail_message(extensions)
|
66
|
-
message = 'Found extensions specified in profiles, but
|
66
|
+
message = 'Found extensions specified in profiles, but NOT used in examples:'
|
67
67
|
extensions.each do |profile, extension|
|
68
68
|
message += "\n Profile: #{profile}, \n\tExtensions: #{extension.join(', ')}" unless extension.empty?
|
69
69
|
end
|
@@ -48,7 +48,7 @@ module Inferno
|
|
48
48
|
else
|
49
49
|
message = 'Found Profiles with not all MustSupports represented:'
|
50
50
|
missing_items_by_profile.each do |profile_url, missing_items|
|
51
|
-
message += "\n\t
|
51
|
+
message += "\n\t#{profile_url}: #{missing_items.join(', ')}"
|
52
52
|
end
|
53
53
|
result = EvaluationResult.new(message, rule: self)
|
54
54
|
end
|
@@ -29,7 +29,7 @@ module Inferno
|
|
29
29
|
message = "Found profiles without examples: \n\t #{unused_profile_urls.join(', ')}"
|
30
30
|
result = EvaluationResult.new(message, rule: self)
|
31
31
|
else
|
32
|
-
message = 'All profiles have
|
32
|
+
message = 'All profiles have examples.'
|
33
33
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
34
34
|
end
|
35
35
|
|
@@ -29,7 +29,7 @@ module Inferno
|
|
29
29
|
message = gen_reference_fail_message(unresolved_references)
|
30
30
|
result = EvaluationResult.new(message, rule: self)
|
31
31
|
else
|
32
|
-
message = 'All references resolve'
|
32
|
+
message = 'All references in examples resolve.'
|
33
33
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
34
34
|
end
|
35
35
|
|
@@ -44,7 +44,7 @@ module Inferno
|
|
44
44
|
"\n Resource (id): #{resource_id} #{reference_detail}"
|
45
45
|
end.join(',')
|
46
46
|
|
47
|
-
"Found unresolved references: #{result_message}"
|
47
|
+
"Found unresolved references in examples: #{result_message}"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -27,11 +27,11 @@ module Inferno
|
|
27
27
|
island_resources.to_a.sort!
|
28
28
|
|
29
29
|
if island_resources.any?
|
30
|
-
message = "Found resources
|
30
|
+
message = "Found resources in examples have no resolved references and are not referenced: #{
|
31
31
|
island_resources.join(', ')}"
|
32
32
|
result = EvaluationResult.new(message, rule: self)
|
33
33
|
else
|
34
|
-
message = 'All resources are reachable'
|
34
|
+
message = 'All resources in examples are reachable.'
|
35
35
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
36
36
|
end
|
37
37
|
|
@@ -23,13 +23,14 @@ module Inferno
|
|
23
23
|
end
|
24
24
|
|
25
25
|
if unused_resource_urls.any?
|
26
|
-
|
26
|
+
unused_resource_list = unused_resource_urls.join("\n\t")
|
27
|
+
message = "Found SearchParameters with no searchable data in examples: \n\t#{unused_resource_list}"
|
27
28
|
result = EvaluationResult.new(message, rule: self)
|
28
29
|
elsif !search_params.empty?
|
29
|
-
message = 'All SearchParameters have examples'
|
30
|
+
message = 'All SearchParameters have examples.'
|
30
31
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
31
32
|
else
|
32
|
-
message = 'IG contains no SearchParameter'
|
33
|
+
message = 'IG contains no SearchParameter.'
|
33
34
|
result = EvaluationResult.new(message, severity: 'information', rule: self)
|
34
35
|
end
|
35
36
|
|
@@ -42,13 +43,13 @@ module Inferno
|
|
42
43
|
# but without it there's no other way to select a value
|
43
44
|
# Return warning if params don't include expression
|
44
45
|
unless param.expression
|
45
|
-
message = "Search parameter #{param.url} doesn't
|
46
|
+
message = "Search parameter #{param.url} doesn't include an expression."
|
46
47
|
result = EvaluationResult.new(message, severity: 'warning', rule: self)
|
47
48
|
context.add_result result
|
48
49
|
return false
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
+
param_used = false
|
52
53
|
|
53
54
|
context.data.each do |resource|
|
54
55
|
next unless param.base.include? resource.resourceType
|
@@ -56,21 +57,31 @@ module Inferno
|
|
56
57
|
begin
|
57
58
|
result = evaluate_fhirpath(resource: resource, path: param.expression)
|
58
59
|
rescue StandardError => e
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
if e.to_s.include? 'Unable to connect to FHIRPath service'
|
61
|
+
result = EvaluationResult.new(e.to_s, severity: 'error', rule: self)
|
62
|
+
else
|
63
|
+
message = "SearchParameter #{param.url} failed to evaluate due to an error. " \
|
64
|
+
"Expression: #{param.expression}. #{e}"
|
65
|
+
result = EvaluationResult.new(message, severity: 'warning', rule: self)
|
66
|
+
end
|
67
|
+
|
62
68
|
context.add_result result
|
63
69
|
|
64
|
-
|
70
|
+
param_used = true
|
65
71
|
break
|
66
72
|
end
|
67
73
|
|
68
74
|
if result.present?
|
69
|
-
|
75
|
+
param_used = true
|
70
76
|
break
|
71
77
|
end
|
72
78
|
end
|
73
|
-
|
79
|
+
param_used
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_message(message_type, message)
|
83
|
+
# No implementation but to prevent error from evaluate_fhirpath().
|
84
|
+
# Without this, will throw "undefined method" error since it expects to be called from a Runnable.
|
74
85
|
end
|
75
86
|
end
|
76
87
|
end
|
@@ -15,7 +15,7 @@ module Inferno
|
|
15
15
|
message = gen_differential_fail_message(unused_differential)
|
16
16
|
result = EvaluationResult.new(message, rule: self)
|
17
17
|
else
|
18
|
-
message = 'All differential fields are
|
18
|
+
message = 'All differential fields are used in examples.'
|
19
19
|
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
20
20
|
end
|
21
21
|
|
@@ -110,7 +110,7 @@ module Inferno
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def gen_differential_fail_message(unused_differential)
|
113
|
-
"Found fields highlighted in the differential view, but not used in
|
113
|
+
"Found fields highlighted in the differential view, but not used in examples: #{
|
114
114
|
unused_differential.map do |url, field|
|
115
115
|
next if field.empty?
|
116
116
|
|
@@ -72,7 +72,7 @@ module Inferno
|
|
72
72
|
# rubocop:disable Metrics/CyclomaticComplexity
|
73
73
|
def create_result_message
|
74
74
|
if value_set_unused.none?
|
75
|
-
message = 'All
|
75
|
+
message = 'All ValueSets are used in examples:'
|
76
76
|
value_set_used.map { |value_set| message += "\n\t#{value_set}" }
|
77
77
|
|
78
78
|
if value_set_unevaluated.any?
|
@@ -82,14 +82,14 @@ module Inferno
|
|
82
82
|
|
83
83
|
EvaluationResult.new(message, severity: 'success', rule: self)
|
84
84
|
else
|
85
|
-
message = '
|
85
|
+
message = 'Found ValueSets with all codes used (at least once) in examples:'
|
86
86
|
value_set_used.map { |url| message += "\n\t#{url}" }
|
87
87
|
|
88
|
-
message += "\nFound unused
|
88
|
+
message += "\nFound unused ValueSets: "
|
89
89
|
value_set_unused.map { |url| message += "\n\t#{url}" }
|
90
90
|
|
91
91
|
if value_set_unevaluated.any?
|
92
|
-
message += "\nFound unevaluated
|
92
|
+
message += "\nFound unevaluated ValueSets: "
|
93
93
|
value_set_unevaluated.map { |url| message += "\n\t#{url}" }
|
94
94
|
end
|
95
95
|
|
@@ -175,10 +175,10 @@ module Inferno
|
|
175
175
|
begin
|
176
176
|
response = call_validator(resource, profile_url)
|
177
177
|
rescue StandardError => e
|
178
|
-
# This could be a complete failure to connect (validator isn't running)
|
179
|
-
# or a timeout (validator took too long to respond).
|
180
178
|
runnable.add_message('error', e.message)
|
181
|
-
|
179
|
+
Application[:logger].error(e.message)
|
180
|
+
|
181
|
+
raise Inferno::Exceptions::ErrorInValidatorException, validator_error_message(e)
|
182
182
|
end
|
183
183
|
|
184
184
|
outcome = operation_outcome_from_validator_response(response, runnable)
|
@@ -340,6 +340,28 @@ module Inferno
|
|
340
340
|
'Validator response was an unexpected format. ' \
|
341
341
|
'Review Messages tab or validator service logs for more information.'
|
342
342
|
end
|
343
|
+
|
344
|
+
# Add a specific error message for specific network problems to help the user
|
345
|
+
#
|
346
|
+
# @private
|
347
|
+
# @param error [Exception] An error exception that happened during evaluator connection
|
348
|
+
# @return [String] A readable error message describing the specific network problem
|
349
|
+
def validator_error_message(error)
|
350
|
+
case error
|
351
|
+
when Faraday::ConnectionFailed
|
352
|
+
"Connection failed to validator at #{url}."
|
353
|
+
when Faraday::TimeoutError
|
354
|
+
"Timeout while connecting to validator at #{url}."
|
355
|
+
when Faraday::SSLError
|
356
|
+
"SSL error connecting to validator at #{url}."
|
357
|
+
when Faraday::ClientError # these are 400s
|
358
|
+
"Client error (4xx) connecting to validator at #{url}."
|
359
|
+
when Faraday::ServerError # these are 500s
|
360
|
+
"Server error (5xx) from validator at #{url}."
|
361
|
+
else
|
362
|
+
"Unable to connect to validator at #{url}."
|
363
|
+
end
|
364
|
+
end
|
343
365
|
end
|
344
366
|
|
345
367
|
# @private
|
@@ -67,7 +67,9 @@ module Inferno
|
|
67
67
|
# This could be a complete failure to connect (fhirpath service isn't running)
|
68
68
|
# or a timeout (fhirpath service took too long to respond).
|
69
69
|
runnable.add_message('error', e.message)
|
70
|
-
|
70
|
+
Application[:logger].error(e.message)
|
71
|
+
|
72
|
+
raise Inferno::Exceptions::ErrorInFhirpathException, evaluator_error_message(e)
|
71
73
|
end
|
72
74
|
|
73
75
|
sanitized_body = remove_invalid_characters(response.body)
|
@@ -108,6 +110,28 @@ module Inferno
|
|
108
110
|
def remove_invalid_characters(string)
|
109
111
|
string.gsub(/[^[:print:]\r\n]+/, '')
|
110
112
|
end
|
113
|
+
|
114
|
+
# Add a specific error message for specific network problems to help the user
|
115
|
+
#
|
116
|
+
# @private
|
117
|
+
# @param error [Exception] An error exception that happened during evaluator connection
|
118
|
+
# @return [String] A readable error message describing the specific network problem
|
119
|
+
def evaluator_error_message(error)
|
120
|
+
case error
|
121
|
+
when Faraday::ConnectionFailed
|
122
|
+
"Connection failed to evaluator at #{url}."
|
123
|
+
when Faraday::TimeoutError
|
124
|
+
"Timeout while connecting to evaluator at #{url}."
|
125
|
+
when Faraday::SSLError
|
126
|
+
"SSL error connecting to evaluator at #{url}."
|
127
|
+
when Faraday::ClientError # these are 400s
|
128
|
+
"Client error (4xx) connecting to evaluator at #{url}."
|
129
|
+
when Faraday::ServerError # these are 500s
|
130
|
+
"Server error (5xx) from evaluator at #{url}."
|
131
|
+
else
|
132
|
+
"Unable to connect to FHIRPath service at #{url}."
|
133
|
+
end
|
134
|
+
end
|
111
135
|
end
|
112
136
|
|
113
137
|
module ClassMethods
|
@@ -12,6 +12,7 @@ module Inferno
|
|
12
12
|
# @option input_params [String] :default The default value for the input
|
13
13
|
# @option input_params [Boolean] :optional Set to true to not require input for test execution
|
14
14
|
# @option input_params [Boolean] :locked If true, the user can not alter the value
|
15
|
+
# @option input_params [Boolean] :hidden If true, the input will not be visible to the user in the UI
|
15
16
|
# @option input_params [Hash] :options Possible input option formats based on input type
|
16
17
|
# @option options [Array] :list_options Array of options for input formats
|
17
18
|
# that require a list of possible values (radio and checkbox)
|
data/lib/inferno/dsl/runnable.rb
CHANGED
@@ -346,6 +346,11 @@ module Inferno
|
|
346
346
|
@all_children ||= []
|
347
347
|
end
|
348
348
|
|
349
|
+
# @private
|
350
|
+
def all_descendants
|
351
|
+
children.flat_map { |child| [child] + child.all_descendants }
|
352
|
+
end
|
353
|
+
|
349
354
|
# @private
|
350
355
|
def suite
|
351
356
|
return self if ancestors.include? Inferno::Entities::TestSuite
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Inferno
|
2
|
+
module DSL
|
3
|
+
# This module manages and locks short IDs, ensuring that short IDs
|
4
|
+
# remain stable and do not change unexpectedly.
|
5
|
+
module ShortIDManager
|
6
|
+
def base_short_id_file_folder
|
7
|
+
File.join(Dir.pwd, 'lib', name.split('::').first.underscore)
|
8
|
+
end
|
9
|
+
|
10
|
+
def short_id_file_name
|
11
|
+
"#{name.demodulize.underscore}_short_id_map.yml"
|
12
|
+
end
|
13
|
+
|
14
|
+
def short_id_file_path
|
15
|
+
File.join(base_short_id_file_folder, short_id_file_name).freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
# Loads and memoizes the short ID map from the YAML file.
|
19
|
+
#
|
20
|
+
# @return [Hash] mapping of runnable IDs to their locked short IDs
|
21
|
+
def short_id_map
|
22
|
+
return unless File.exist?(short_id_file_path)
|
23
|
+
|
24
|
+
@short_id_map ||= YAML.load_file(short_id_file_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
# Assigns locked short IDs to all descendant runnables based on the short ID map.
|
29
|
+
#
|
30
|
+
# This method is called at boot time.
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
def assign_short_ids
|
34
|
+
return unless short_id_map
|
35
|
+
|
36
|
+
all_descendants.each do |runnable|
|
37
|
+
new_short_id = short_id_map.fetch(runnable.id)
|
38
|
+
runnable.short_id(new_short_id)
|
39
|
+
rescue KeyError
|
40
|
+
Inferno::Application['logger'].warn("No short id defined for #{runnable.id}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Builds and memoizes the current mapping of runnable IDs to their short IDs.
|
45
|
+
#
|
46
|
+
# @return [Hash] current short ID mapping
|
47
|
+
def current_short_id_map
|
48
|
+
@current_short_id_map ||=
|
49
|
+
all_descendants.each_with_object({}) do |runnable, mapping|
|
50
|
+
mapping[runnable.id] = runnable.short_id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -14,6 +14,7 @@ module Inferno
|
|
14
14
|
:optional,
|
15
15
|
:options,
|
16
16
|
:locked,
|
17
|
+
:hidden,
|
17
18
|
:value
|
18
19
|
].freeze
|
19
20
|
include Entities::Attributes
|
@@ -21,15 +22,15 @@ module Inferno
|
|
21
22
|
# These attributes require special handling when merging input
|
22
23
|
# definitions.
|
23
24
|
UNINHERITABLE_ATTRIBUTES = [
|
24
|
-
# Locking an input only has meaning at the level it is
|
25
|
+
# Locking or hiding an input only has meaning at the level it is applied.
|
25
26
|
# Consider:
|
26
27
|
# - ParentGroup
|
27
28
|
# - Group 1, input :a
|
28
|
-
# - Group 2, input :a, locked: true
|
29
|
-
# The input 'a' should
|
30
|
-
#
|
31
|
-
# ParentGroup.
|
29
|
+
# - Group 2, input :a, locked: true, hidden: true, optional: true
|
30
|
+
# The input 'a' should only be locked or hidden when running Group 2 in isolation.
|
31
|
+
# It should not be locked or hidden when running Group 1 or the ParentGroup.
|
32
32
|
:locked,
|
33
|
+
:hidden,
|
33
34
|
# Input type is sometimes only a UI concern (e.g. text vs. textarea), so
|
34
35
|
# it is common to not redeclare the type everywhere it's used and needs
|
35
36
|
# special handling to avoid clobbering the type with the default (text)
|
@@ -50,6 +51,14 @@ module Inferno
|
|
50
51
|
|
51
52
|
raise Exceptions::UnknownAttributeException.new(bad_params, self.class) if bad_params.present?
|
52
53
|
|
54
|
+
if params[:hidden] && !params[:optional] && !params[:locked]
|
55
|
+
raise Exceptions::InvalidAttributeException.new(
|
56
|
+
:hidden,
|
57
|
+
self.class,
|
58
|
+
"Input '#{params[:name]}' cannot be hidden unless it is optional or locked."
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
53
62
|
params
|
54
63
|
.compact
|
55
64
|
.each { |key, value| send("#{key}=", value) }
|