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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/cli/evaluate/database.yml +15 -0
  3. data/lib/inferno/apps/cli/evaluate/docker-compose.evaluate.yml +16 -0
  4. data/lib/inferno/apps/cli/evaluate.rb +52 -4
  5. data/lib/inferno/apps/cli/main.rb +5 -1
  6. data/lib/inferno/apps/cli/requirements.rb +28 -0
  7. data/lib/inferno/apps/cli/requirements_exporter.rb +194 -0
  8. data/lib/inferno/apps/cli/suite.rb +21 -0
  9. data/lib/inferno/apps/cli/templates/lib/%library_name%/example_suite/patient_group.rb.tt +141 -0
  10. data/lib/inferno/apps/cli/templates/lib/%library_name%/example_suite.rb.tt +128 -0
  11. data/lib/inferno/apps/cli/templates/lib/%library_name%/metadata.rb.tt +65 -3
  12. data/lib/inferno/apps/cli/templates/lib/%library_name%/suite.rb.tt +2 -2
  13. data/lib/inferno/apps/cli/templates/lib/%library_name%/version.rb.tt +1 -0
  14. data/lib/inferno/apps/cli/templates/lib/%library_name%.rb.tt +1 -1
  15. data/lib/inferno/apps/web/controllers/requirements/show.rb +18 -0
  16. data/lib/inferno/apps/web/controllers/test_suites/requirements/index.rb +29 -0
  17. data/lib/inferno/apps/web/router.rb +7 -0
  18. data/lib/inferno/apps/web/serializers/input.rb +1 -0
  19. data/lib/inferno/apps/web/serializers/requirement.rb +18 -0
  20. data/lib/inferno/apps/web/serializers/requirement_set.rb +13 -0
  21. data/lib/inferno/apps/web/serializers/test.rb +1 -0
  22. data/lib/inferno/apps/web/serializers/test_group.rb +1 -0
  23. data/lib/inferno/apps/web/serializers/test_suite.rb +11 -0
  24. data/lib/inferno/config/boot/requirements.rb +40 -0
  25. data/lib/inferno/config/boot/suites.rb +3 -0
  26. data/lib/inferno/dsl/fhir_evaluation/default.yml +68 -0
  27. data/lib/inferno/dsl/fhir_evaluation/evaluator.rb +3 -5
  28. data/lib/inferno/dsl/fhir_evaluation/rules/all_defined_extensions_have_examples.rb +2 -2
  29. data/lib/inferno/dsl/fhir_evaluation/rules/all_extensions_used.rb +76 -0
  30. data/lib/inferno/dsl/fhir_evaluation/rules/all_must_supports_present.rb +1 -1
  31. data/lib/inferno/dsl/fhir_evaluation/rules/all_profiles_have_examples.rb +1 -1
  32. data/lib/inferno/dsl/fhir_evaluation/rules/all_references_resolve.rb +2 -2
  33. data/lib/inferno/dsl/fhir_evaluation/rules/all_resources_reachable.rb +2 -2
  34. data/lib/inferno/dsl/fhir_evaluation/rules/all_search_parameters_have_examples.rb +22 -11
  35. data/lib/inferno/dsl/fhir_evaluation/rules/differential_content_has_examples.rb +124 -0
  36. data/lib/inferno/dsl/fhir_evaluation/rules/value_sets_demonstrate.rb +233 -0
  37. data/lib/inferno/dsl/fhir_resource_navigation.rb +11 -2
  38. data/lib/inferno/dsl/fhir_resource_validation.rb +25 -3
  39. data/lib/inferno/dsl/fhirpath_evaluation.rb +25 -1
  40. data/lib/inferno/dsl/input_output_handling.rb +1 -0
  41. data/lib/inferno/dsl/must_support_assessment.rb +15 -3
  42. data/lib/inferno/dsl/requirement_set.rb +82 -0
  43. data/lib/inferno/dsl/runnable.rb +27 -0
  44. data/lib/inferno/dsl/short_id_manager.rb +55 -0
  45. data/lib/inferno/dsl/suite_requirements.rb +46 -0
  46. data/lib/inferno/entities/ig.rb +4 -0
  47. data/lib/inferno/entities/input.rb +14 -5
  48. data/lib/inferno/entities/requirement.rb +75 -0
  49. data/lib/inferno/entities/test.rb +3 -1
  50. data/lib/inferno/entities/test_group.rb +3 -1
  51. data/lib/inferno/entities/test_suite.rb +4 -0
  52. data/lib/inferno/exceptions.rb +6 -0
  53. data/lib/inferno/public/237.bundle.js +1 -1
  54. data/lib/inferno/public/bundle.js +54 -54
  55. data/lib/inferno/public/bundle.js.LICENSE.txt +3 -36
  56. data/lib/inferno/repositories/igs.rb +1 -2
  57. data/lib/inferno/repositories/requirements.rb +120 -0
  58. data/lib/inferno/version.rb +1 -1
  59. data/spec/shared/test_kit_examples.rb +32 -0
  60. metadata +36 -3
  61. 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
- This is a big markdown description of the test kit.
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
- # tags ['SMART App Launch', 'US Core']
12
- # last_updated '2024-03-07'
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: :oauth_credentials,
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
- oauth_credentials :credentials
22
+ auth_info :credentials
23
23
  end
24
24
 
25
25
  # All FHIR validation requests will use this FHIR validator
@@ -1,3 +1,4 @@
1
1
  module <%= module_name %>
2
2
  VERSION = '0.0.0'.freeze
3
+ LAST_UPDATED = '<%= Date.today.strftime("%Y-%m-%d") %>'.freeze # TODO: update next release
3
4
  end
@@ -1 +1 @@
1
- require_relative '<%= library_name %>/suite'
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
@@ -14,6 +14,7 @@ module Inferno
14
14
  field :optional, if: :field_present?
15
15
  field :options, if: :field_present?
16
16
  field :locked, if: :field_present?
17
+ field :hidden, if: :field_present?
17
18
  field :value, if: :field_present?
18
19
  end
19
20
  end
@@ -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
@@ -0,0 +1,13 @@
1
+ require_relative 'serializer'
2
+
3
+ module Inferno
4
+ module Web
5
+ module Serializers
6
+ class RequirementSet < Serializer
7
+ identifier :identifier
8
+
9
+ field :title
10
+ end
11
+ end
12
+ end
13
+ end
@@ -20,6 +20,7 @@ module Inferno
20
20
  field :input_instructions
21
21
  field :user_runnable?, name: :user_runnable
22
22
  field :optional?, name: :optional
23
+ field :verifies_requirements, if: :field_present?
23
24
  end
24
25
  end
25
26
  end
@@ -32,6 +32,7 @@ module Inferno
32
32
  Input.render_as_hash(group.available_inputs(suite_options).values)
33
33
  end
34
34
  field :output_definitions, name: :outputs, extractor: HashValueExtractor
35
+ field :verifies_requirements, if: :field_present?
35
36
  end
36
37
  end
37
38
  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
- active_rules << rulename if rule_details['Enabled']
30
- end
28
+ next unless rule_details['Enabled']
31
29
 
32
- Rule.descendants.each do |rule|
33
- rule.new.check(context) if active_rules.include?(rule.name.demodulize)
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 unused extensions defined in the IG: \n\t #{unused_extensions.join(', ')}"
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 are represented in examples'
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\t#{profile_url}: #{missing_items.join(', ')}"
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 example instances.'
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 that have no resolved references and are not referenced: #{
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
- message = "Found SearchParameters with no searchable data: \n\t#{unused_resource_urls.join(' ,')}"
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 have an expression."
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
- used = false
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
- message = "SearchParameter #{param.url} failed to evaluate due to an error. " \
60
- "Expression: #{param.expression}. #{e}"
61
- result = EvaluationResult.new(message)
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
- used = true
70
+ param_used = true
65
71
  break
66
72
  end
67
73
 
68
74
  if result.present?
69
- used = true
75
+ param_used = true
70
76
  break
71
77
  end
72
78
  end
73
- used
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