inferno_core 0.6.8 → 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%/suite.rb.tt +2 -2
- 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/controllers/requirements/show.rb +18 -0
- data/lib/inferno/apps/web/controllers/test_suites/requirements/index.rb +29 -0
- data/lib/inferno/apps/web/router.rb +7 -0
- data/lib/inferno/apps/web/serializers/input.rb +1 -0
- data/lib/inferno/apps/web/serializers/requirement.rb +18 -0
- data/lib/inferno/apps/web/serializers/requirement_set.rb +13 -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 +11 -0
- data/lib/inferno/config/boot/requirements.rb +40 -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 +76 -0
- 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 +124 -0
- data/lib/inferno/dsl/fhir_evaluation/rules/value_sets_demonstrate.rb +233 -0
- data/lib/inferno/dsl/fhir_resource_navigation.rb +11 -2
- 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/must_support_assessment.rb +15 -3
- data/lib/inferno/dsl/requirement_set.rb +82 -0
- data/lib/inferno/dsl/runnable.rb +27 -0
- data/lib/inferno/dsl/short_id_manager.rb +55 -0
- data/lib/inferno/dsl/suite_requirements.rb +46 -0
- data/lib/inferno/entities/ig.rb +4 -0
- data/lib/inferno/entities/input.rb +14 -5
- data/lib/inferno/entities/requirement.rb +75 -0
- data/lib/inferno/entities/test.rb +3 -1
- data/lib/inferno/entities/test_group.rb +3 -1
- data/lib/inferno/entities/test_suite.rb +4 -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/igs.rb +1 -2
- data/lib/inferno/repositories/requirements.rb +120 -0
- data/lib/inferno/version.rb +1 -1
- data/spec/shared/test_kit_examples.rb +32 -0
- metadata +36 -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 %>
|
@@ -13,13 +13,13 @@ module <%= module_name %>
|
|
13
13
|
|
14
14
|
input :credentials,
|
15
15
|
title: 'OAuth Credentials',
|
16
|
-
type: :
|
16
|
+
type: :auth_info,
|
17
17
|
optional: true
|
18
18
|
|
19
19
|
# All FHIR requests in this suite will use this FHIR client
|
20
20
|
fhir_client do
|
21
21
|
url :url
|
22
|
-
|
22
|
+
auth_info :credentials
|
23
23
|
end
|
24
24
|
|
25
25
|
# All FHIR validation requests will use this FHIR validator
|
@@ -1 +1 @@
|
|
1
|
-
require_relative '<%= library_name %>/
|
1
|
+
require_relative '<%= library_name %>/example_suite'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative '../../serializers/requirement'
|
2
|
+
|
3
|
+
module Inferno
|
4
|
+
module Web
|
5
|
+
module Controllers
|
6
|
+
module Requirements
|
7
|
+
class Show < Controller
|
8
|
+
def handle(req, res)
|
9
|
+
requirement = repo.find(req.params[:id])
|
10
|
+
halt 404 if requirement.nil?
|
11
|
+
|
12
|
+
res.body = serialize(requirement)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Inferno
|
2
|
+
module Web
|
3
|
+
module Controllers
|
4
|
+
module TestSuites
|
5
|
+
module Requirements
|
6
|
+
class Index < Controller
|
7
|
+
include Import[test_suites_repo: 'inferno.repositories.test_suites']
|
8
|
+
include Import[test_sessions_repo: 'inferno.repositories.test_sessions']
|
9
|
+
|
10
|
+
def handle(req, res)
|
11
|
+
test_suite = test_suites_repo.find(req.params[:id])
|
12
|
+
halt 404, "Test Suite `#{req.params[:id]}` not found" if test_suite.nil?
|
13
|
+
|
14
|
+
test_session = nil
|
15
|
+
if req.params[:session_id]
|
16
|
+
test_session = test_sessions_repo.find(req.params[:session_id])
|
17
|
+
halt 404, "Test session `#{req.params[:session_id]}` not found" if test_session.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
requirements = repo.requirements_for_suite(test_suite.id, test_session&.id)
|
21
|
+
|
22
|
+
res.body = serialize(requirements)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -47,6 +47,13 @@ module Inferno
|
|
47
47
|
put '/:id/check_configuration',
|
48
48
|
to: Inferno::Web::Controllers::TestSuites::CheckConfiguration,
|
49
49
|
as: :check_configuration
|
50
|
+
get ':id/requirements',
|
51
|
+
to: Inferno::Web::Controllers::TestSuites::Requirements::Index,
|
52
|
+
as: :requirements
|
53
|
+
end
|
54
|
+
|
55
|
+
scope 'requirements' do
|
56
|
+
get '/:id', to: Inferno::Web::Controllers::Requirements::Show, as: :show
|
50
57
|
end
|
51
58
|
|
52
59
|
get '/requests/:id', to: Inferno::Web::Controllers::Requests::Show, as: :requests_show
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'serializer'
|
2
|
+
|
3
|
+
module Inferno
|
4
|
+
module Web
|
5
|
+
module Serializers
|
6
|
+
class Requirement < Serializer
|
7
|
+
identifier :id
|
8
|
+
|
9
|
+
field :requirement
|
10
|
+
field :conformance
|
11
|
+
field :actor
|
12
|
+
field :sub_requirements
|
13
|
+
field :conditionality
|
14
|
+
field :url, if: :field_present?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'preset'
|
2
|
+
require_relative 'requirement_set'
|
2
3
|
require_relative 'suite_option'
|
3
4
|
require_relative 'test_group'
|
4
5
|
|
@@ -27,6 +28,7 @@ module Inferno
|
|
27
28
|
|
28
29
|
view :full do
|
29
30
|
include_view :summary
|
31
|
+
|
30
32
|
field :test_groups do |suite, options|
|
31
33
|
suite_options = options[:suite_options]
|
32
34
|
TestGroup.render_as_hash(suite.groups(suite_options), suite_options:)
|
@@ -36,6 +38,15 @@ module Inferno
|
|
36
38
|
suite_options = options[:suite_options]
|
37
39
|
Input.render_as_hash(suite.available_inputs(suite_options).values)
|
38
40
|
end
|
41
|
+
field :requirement_sets, if: :field_present? do |suite, options|
|
42
|
+
selected_options = options[:suite_options] || []
|
43
|
+
requirement_sets = suite.requirement_sets.select do |requirement_set|
|
44
|
+
requirement_set.suite_options.all? { |suite_option| selected_options.include? suite_option }
|
45
|
+
end
|
46
|
+
|
47
|
+
RequirementSet.render_as_hash(requirement_sets)
|
48
|
+
end
|
49
|
+
field :verifies_requirements, if: :field_present?
|
39
50
|
end
|
40
51
|
end
|
41
52
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../../repositories/requirements'
|
2
|
+
|
3
|
+
Inferno::Application.register_provider(:requirements) do
|
4
|
+
prepare do
|
5
|
+
target_container.start :suites
|
6
|
+
|
7
|
+
requirements_repo = Inferno::Repositories::Requirements.new
|
8
|
+
|
9
|
+
test_kit_gems =
|
10
|
+
Bundler
|
11
|
+
.definition
|
12
|
+
.specs
|
13
|
+
.select { |spec| spec.metadata.fetch('inferno_test_kit', 'false').casecmp? 'true' }
|
14
|
+
|
15
|
+
files_to_load = Dir.glob(['lib/*test_kit/requirements/*.csv'])
|
16
|
+
|
17
|
+
if ENV['LOAD_DEV_SUITES'].present?
|
18
|
+
ENV['LOAD_DEV_SUITES'].split(',').map(&:strip).reject(&:empty?).each do |suite|
|
19
|
+
files_to_load.concat Dir.glob(File.join(Inferno::Application.root, 'dev_suites', suite, 'requirements',
|
20
|
+
'*.csv'))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
files_to_load +=
|
25
|
+
test_kit_gems.flat_map do |gem|
|
26
|
+
[
|
27
|
+
Dir.glob([File.join(gem.full_gem_path, 'lib', '*test_kit', 'requirements', '*.csv')])
|
28
|
+
].flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
files_to_load.compact!
|
32
|
+
files_to_load.reject! { |file| file.include?('out_of_scope') }
|
33
|
+
files_to_load.map! { |path| File.realpath(path) }
|
34
|
+
files_to_load.uniq!
|
35
|
+
|
36
|
+
files_to_load.each do |path|
|
37
|
+
requirements_repo.insert_from_file(path)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -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
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Inferno
|
4
|
+
module DSL
|
5
|
+
module FHIREvaluation
|
6
|
+
module Rules
|
7
|
+
class AllExtensionsUsed < Rule
|
8
|
+
def check(context)
|
9
|
+
all_extensions = collect_profile_extensions(context.ig.profiles)
|
10
|
+
unused_extensions = remove_found_resource_extensions(all_extensions, context.data)
|
11
|
+
if unused_extensions.any? { |_profile, extensions| !extensions.empty? }
|
12
|
+
message = get_fail_message(unused_extensions)
|
13
|
+
result = EvaluationResult.new(message, rule: self)
|
14
|
+
else
|
15
|
+
message = 'All extensions specified in profiles are used in examples.'
|
16
|
+
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
17
|
+
end
|
18
|
+
|
19
|
+
context.add_result result
|
20
|
+
end
|
21
|
+
|
22
|
+
def collect_profile_extensions(profiles)
|
23
|
+
extensions = Hash.new { |extension, profile| extension[profile] = Set.new }
|
24
|
+
profiles.each do |profile|
|
25
|
+
profile.each_element do |value, metadata|
|
26
|
+
next unless metadata['type'] == 'ElementDefinition'
|
27
|
+
|
28
|
+
path_end = value.id.split('.')[-1]
|
29
|
+
next unless path_end.include?('extension')
|
30
|
+
|
31
|
+
value.type.each do |element_definition|
|
32
|
+
element_definition.profile.each do |extension_url|
|
33
|
+
extensions[profile.url].add(extension_url)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
extensions
|
39
|
+
end
|
40
|
+
|
41
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
42
|
+
def remove_found_resource_extensions(extensions, examples)
|
43
|
+
unused_extensions = extensions.dup
|
44
|
+
examples.each do |resource|
|
45
|
+
resource.each_element do |value, _metadata, path|
|
46
|
+
path_elements = path.split('.')
|
47
|
+
next unless path_elements.length > 1
|
48
|
+
|
49
|
+
next unless path_elements[-2].include?('extension') && path_elements[-1] == 'url'
|
50
|
+
|
51
|
+
profiles = resource&.meta&.profile || []
|
52
|
+
update_unused_extensions(profiles, value, unused_extensions, extensions)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
unused_extensions
|
56
|
+
end
|
57
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
58
|
+
|
59
|
+
def update_unused_extensions(profiles, value, unused_extensions, extensions)
|
60
|
+
profiles.each do |profile|
|
61
|
+
unused_extensions[profile].delete(value) if extensions.key?(profile)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_fail_message(extensions)
|
66
|
+
message = 'Found extensions specified in profiles, but NOT used in examples:'
|
67
|
+
extensions.each do |profile, extension|
|
68
|
+
message += "\n Profile: #{profile}, \n\tExtensions: #{extension.join(', ')}" unless extension.empty?
|
69
|
+
end
|
70
|
+
message
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
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
|