us_quality_core_test_kit 0.1.1 → 0.1.2
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/NOTICE.md +12 -0
- data/config/presets/{localhost_preset.json → inferno_reference_server_preset.json.erb} +2 -2
- data/lib/us_quality_core_test_kit/client/generator/example_client_generator.rb +0 -1
- data/lib/us_quality_core_test_kit/client/generator/tags_generator.rb +0 -1
- data/lib/us_quality_core_test_kit/client/server_proxy.rb +2 -6
- data/lib/us_quality_core_test_kit/date_search_validation.rb +122 -0
- data/lib/us_quality_core_test_kit/fhir_resource_navigation.rb +9 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/devicenotrequested/devicenotrequested_validation_test.rb +3 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/devicerequest/devicerequest_validation_test.rb +3 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/patient/patient_validation_test.rb +4 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/us_quality_core_test_suite.rb +1 -2
- data/lib/us_quality_core_test_kit/generator/group_generator.rb +69 -3
- data/lib/us_quality_core_test_kit/generator/group_metadata.rb +100 -4
- data/lib/us_quality_core_test_kit/generator/group_metadata_extractor.rb +184 -8
- data/lib/us_quality_core_test_kit/generator/ig_loader.rb +47 -3
- data/lib/us_quality_core_test_kit/generator/ig_metadata.rb +51 -2
- data/lib/us_quality_core_test_kit/generator/ig_metadata_extractor.rb +25 -6
- data/lib/us_quality_core_test_kit/generator/ig_resources.rb +27 -3
- data/lib/us_quality_core_test_kit/generator/must_support_metadata_extractor.rb +366 -9
- data/lib/us_quality_core_test_kit/generator/must_support_test_generator.rb +54 -3
- data/lib/us_quality_core_test_kit/generator/provenance_revinclude_search_test_generator.rb +149 -3
- data/lib/us_quality_core_test_kit/generator/read_test_generator.rb +54 -3
- data/lib/us_quality_core_test_kit/generator/reference_resolution_test_generator.rb +57 -3
- data/lib/us_quality_core_test_kit/generator/search_definition_metadata_extractor.rb +181 -5
- data/lib/us_quality_core_test_kit/generator/search_metadata_extractor.rb +61 -3
- data/lib/us_quality_core_test_kit/generator/search_test_generator.rb +186 -3
- data/lib/us_quality_core_test_kit/generator/suite_generator.rb +35 -8
- data/lib/us_quality_core_test_kit/generator/terminology_binding_metadata_extractor.rb +107 -3
- data/lib/us_quality_core_test_kit/generator/validation_test_generator.rb +83 -4
- data/lib/us_quality_core_test_kit/generator/value_extractor.rb +147 -3
- data/lib/us_quality_core_test_kit/must_support_test.rb +15 -3
- data/lib/us_quality_core_test_kit/primitive_type.rb +7 -0
- data/lib/us_quality_core_test_kit/read_test.rb +99 -4
- data/lib/us_quality_core_test_kit/reference_resolution_test.rb +164 -3
- data/lib/us_quality_core_test_kit/resource_search_param_checker.rb +138 -0
- data/lib/us_quality_core_test_kit/search_test.rb +681 -46
- data/lib/us_quality_core_test_kit/search_test_properties.rb +53 -3
- data/lib/us_quality_core_test_kit/validation_test.rb +51 -4
- data/lib/us_quality_core_test_kit/version.rb +2 -2
- data/lib/us_quality_core_test_kit/well_known_code_systems.rb +23 -0
- metadata +8 -23
|
@@ -1,10 +1,114 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/terminology_binding_metadata_extractor'
|
|
4
|
-
|
|
5
3
|
module USQualityCoreTestKit
|
|
6
4
|
class Generator
|
|
7
|
-
class TerminologyBindingMetadataExtractor
|
|
5
|
+
class TerminologyBindingMetadataExtractor
|
|
6
|
+
attr_accessor :profile_elements, :ig_resources, :resource
|
|
7
|
+
|
|
8
|
+
def initialize(profile_elements, ig_resources, resource)
|
|
9
|
+
self.profile_elements = profile_elements
|
|
10
|
+
self.ig_resources = ig_resources
|
|
11
|
+
self.resource = resource
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def terminology_bindings
|
|
15
|
+
(element_terminology_bindings + extension_terminology_bindings).compact
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def element_has_fixed_value?(element)
|
|
19
|
+
case element.type.first.code
|
|
20
|
+
when 'Quantity'
|
|
21
|
+
code = profile_elements.find { |e| e.path == "#{element.path}.code" }
|
|
22
|
+
system = profile_elements.find { |e| e.path == "#{element.path}.system" }
|
|
23
|
+
code&.fixedCode || system&.fixedUri
|
|
24
|
+
when 'code'
|
|
25
|
+
element.fixedCode.present?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def element_has_optional_binding_slice?(element)
|
|
30
|
+
element.sliceName.present? && element.min.zero?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def profile_elements_with_bindings
|
|
34
|
+
profile_elements
|
|
35
|
+
.select { |element| element.binding.present? && element.binding.strength == 'required' }
|
|
36
|
+
.reject { |element| element_has_fixed_value?(element) || element_has_optional_binding_slice?(element) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def element_terminology_bindings
|
|
40
|
+
profile_elements_with_bindings.map do |element|
|
|
41
|
+
binding = {
|
|
42
|
+
type: element.type.first.code,
|
|
43
|
+
strength: element.binding.strength,
|
|
44
|
+
system: element.binding.valueSet&.split('|')&.first,
|
|
45
|
+
path: element.path.gsub('[x]', '').gsub("#{resource}.", '')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
binding[:required_binding_slice] = true if element.sliceName.present? && element.min.positive?
|
|
49
|
+
|
|
50
|
+
binding
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def extension_profile_elements
|
|
55
|
+
profile_elements
|
|
56
|
+
.select { |element| element.type&.first&.code == 'Extension' }
|
|
57
|
+
.select { |element| extension_profile_url(element).present? }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def extension_profile_url(extension)
|
|
61
|
+
extension.type.first.profile&.first
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def extension_terminology_bindings
|
|
65
|
+
extension_profile_elements
|
|
66
|
+
.flat_map do |extension_profile_element|
|
|
67
|
+
url = extension_profile_url(extension_profile_element)
|
|
68
|
+
extension = ig_resources.profile_by_url(url)
|
|
69
|
+
|
|
70
|
+
# TODO: Temporaray fix for extension defined out of US Core. FI-1623
|
|
71
|
+
next if extension.nil?
|
|
72
|
+
|
|
73
|
+
elements = extension.snapshot.element
|
|
74
|
+
elements_with_bindings = elements.select do |element|
|
|
75
|
+
element.binding.present? && !element.id.include?('Extension.extension')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
elements_with_bindings.map do |element|
|
|
79
|
+
{
|
|
80
|
+
type: element.type.first.code,
|
|
81
|
+
strength: element.binding.strength,
|
|
82
|
+
system: element.binding.valueSet&.split('|')&.first,
|
|
83
|
+
path: element.path.gsub('[x]', '').gsub('Extension.', ''),
|
|
84
|
+
extensions: [url]
|
|
85
|
+
}
|
|
86
|
+
end + nested_extension_terminology_bindings(elements, url)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def nested_extension_terminology_bindings(elements, extension_url)
|
|
91
|
+
nested_extensions = elements.select { |element| element.path == 'Extension.extension' }
|
|
92
|
+
nested_extensions.flat_map do |nested_extension|
|
|
93
|
+
nested_extension_element = elements.find { |element| element.id == "#{nested_extension.id}.url" }
|
|
94
|
+
next unless nested_extension_element.present?
|
|
95
|
+
|
|
96
|
+
nested_extension_url = nested_extension_element.fixedUri
|
|
97
|
+
nested_elements_with_bindings = elements.select do |element|
|
|
98
|
+
element.id.include?(nested_extension.id) && element.binding.present?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
nested_elements_with_bindings.map do |element|
|
|
102
|
+
{
|
|
103
|
+
type: element.type.first.code,
|
|
104
|
+
strength: element.binding.strength,
|
|
105
|
+
system: element.binding.valueSet&.split('|')&.first,
|
|
106
|
+
path: element.path.gsub('[x]', '').gsub('Extension.extension.', ''),
|
|
107
|
+
extensions: [extension_url, nested_extension_url]
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
8
112
|
end
|
|
9
113
|
end
|
|
10
114
|
end
|
|
@@ -1,13 +1,71 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/validation_test_generator'
|
|
4
|
-
|
|
5
3
|
require_relative 'naming'
|
|
6
4
|
require_relative 'special_cases'
|
|
7
5
|
|
|
8
6
|
module USQualityCoreTestKit
|
|
9
7
|
class Generator
|
|
10
|
-
class ValidationTestGenerator
|
|
8
|
+
class ValidationTestGenerator
|
|
9
|
+
attr_accessor :group_metadata, :medication_request_metadata, :base_output_dir
|
|
10
|
+
|
|
11
|
+
def initialize(group_metadata, medication_request_metadata = nil, base_output_dir:)
|
|
12
|
+
self.group_metadata = group_metadata
|
|
13
|
+
self.medication_request_metadata = medication_request_metadata
|
|
14
|
+
self.base_output_dir = base_output_dir
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def output
|
|
18
|
+
@output ||= ERB.new(template, trim_mode: '-').result(binding)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def base_output_file_name
|
|
22
|
+
"#{class_name.underscore}.rb"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def output_file_directory
|
|
26
|
+
File.join(base_output_dir, directory_name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def output_file_name
|
|
30
|
+
File.join(output_file_directory, base_output_file_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def profile_url
|
|
34
|
+
group_metadata.profile_url
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def profile_name
|
|
38
|
+
group_metadata.profile_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def profile_version
|
|
42
|
+
group_metadata.profile_version
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def resource_type
|
|
46
|
+
group_metadata.resource
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def conformance_expectation
|
|
50
|
+
read_interaction[:expectation]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def generate
|
|
54
|
+
FileUtils.mkdir_p(output_file_directory)
|
|
55
|
+
File.write(output_file_name, output)
|
|
56
|
+
|
|
57
|
+
test_metadata = {
|
|
58
|
+
id: test_id,
|
|
59
|
+
file_name: base_output_file_name
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if resource_type == 'Medication'
|
|
63
|
+
medication_request_metadata.add_test(**test_metadata)
|
|
64
|
+
else
|
|
65
|
+
group_metadata.add_test(**test_metadata)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
11
69
|
class << self
|
|
12
70
|
def generate(ig_metadata, base_output_dir)
|
|
13
71
|
ig_metadata.groups
|
|
@@ -49,7 +107,7 @@ module USQualityCoreTestKit
|
|
|
49
107
|
end
|
|
50
108
|
|
|
51
109
|
def description
|
|
52
|
-
<<~DESCRIPTION
|
|
110
|
+
base_description = <<~DESCRIPTION
|
|
53
111
|
#{description_intro}
|
|
54
112
|
It verifies the presence of mandatory elements and that elements with
|
|
55
113
|
required bindings contain appropriate values. CodeableConcept element
|
|
@@ -57,6 +115,27 @@ module USQualityCoreTestKit
|
|
|
57
115
|
to the bound ValueSet. Quantity, Coding, and code element bindings will
|
|
58
116
|
fail if their code/system are not found in the valueset.
|
|
59
117
|
DESCRIPTION
|
|
118
|
+
|
|
119
|
+
filter_note = validation_message_filter_note
|
|
120
|
+
return base_description unless filter_note
|
|
121
|
+
|
|
122
|
+
"#{base_description}\n#{filter_note}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def validation_message_filter_note
|
|
126
|
+
case resource_type
|
|
127
|
+
when 'Patient'
|
|
128
|
+
<<~NOTE
|
|
129
|
+
Note: This test ignores validator messages for `Patient.extension`. This is a workaround for
|
|
130
|
+
a known validator package interaction where CQL can load US Core 7 definitions into the
|
|
131
|
+
validator session even though US Quality Core 0.5.0 is based on US Core 6.1.0.
|
|
132
|
+
NOTE
|
|
133
|
+
when 'DeviceRequest'
|
|
134
|
+
<<~NOTE
|
|
135
|
+
Note: This test ignores a known validator slicing resolution message for
|
|
136
|
+
`DeviceRequest.modifierExtension`.
|
|
137
|
+
NOTE
|
|
138
|
+
end
|
|
60
139
|
end
|
|
61
140
|
|
|
62
141
|
def description_intro
|
|
@@ -1,10 +1,154 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/value_extractor'
|
|
4
|
-
|
|
5
3
|
module USQualityCoreTestKit
|
|
6
4
|
class Generator
|
|
7
|
-
class ValueExactor
|
|
5
|
+
class ValueExactor
|
|
6
|
+
attr_accessor :ig_resources, :resource, :profile_elements
|
|
7
|
+
|
|
8
|
+
def initialize(ig_resources, resource, profile_elements)
|
|
9
|
+
self.ig_resources = ig_resources
|
|
10
|
+
self.resource = resource
|
|
11
|
+
self.profile_elements = profile_elements
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def values_from_fixed_codes(profile_element, type)
|
|
15
|
+
return [] unless type == 'CodeableConcept'
|
|
16
|
+
|
|
17
|
+
elements = profile_elements.select do |element|
|
|
18
|
+
element.path == "#{profile_element.path}.coding.code" && element.fixedCode.present?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
elements.map(&:fixedCode)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def values_from_pattern_coding(profile_element, type)
|
|
25
|
+
return [] unless type == 'CodeableConcept'
|
|
26
|
+
|
|
27
|
+
elements = profile_elements.select do |element|
|
|
28
|
+
element.path == "#{profile_element.path}.coding" && element.patternCoding.present?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
elements.map { |element| element.patternCoding.code }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def values_from_pattern_codeable_concept(profile_element, type)
|
|
35
|
+
return [] unless type == 'CodeableConcept'
|
|
36
|
+
|
|
37
|
+
elements = profile_elements.select do |element|
|
|
38
|
+
element.path == profile_element.path && element.patternCodeableConcept.present? && element.min.positive?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
elements.map { |element| element.patternCodeableConcept.coding.first.code }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def value_set_binding(the_element)
|
|
45
|
+
the_element&.binding
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def value_set(the_element)
|
|
49
|
+
binding = value_set_binding(the_element)
|
|
50
|
+
target_valueset = binding&.valueSet
|
|
51
|
+
|
|
52
|
+
additional_binding_ext = binding&.extension&.find do |ext|
|
|
53
|
+
ext.url == 'http://hl7.org/fhir/tools/StructureDefinition/additional-binding' &&
|
|
54
|
+
ext.extension.any? { |sub_ext| sub_ext.url == 'purpose' && sub_ext.valueCode == 'minimum' }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
min_valueset_ext = binding&.extension&.find do |ext|
|
|
58
|
+
ext.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-minValueSet'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if additional_binding_ext.present?
|
|
62
|
+
min_valueset = additional_binding_ext.extension.find { |ext| ext.url == 'valueSet' }
|
|
63
|
+
|
|
64
|
+
target_valueset = min_valueset.valueCanonical if min_valueset.present?
|
|
65
|
+
elsif min_valueset_ext.present?
|
|
66
|
+
target_valueset = min_valueset_ext.valueCanonical
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
ig_resources.value_set_by_url(target_valueset)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def bound_systems(the_element)
|
|
73
|
+
bound_systems_from_valueset(value_set(the_element))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def bound_systems_from_valueset(value_set)
|
|
77
|
+
value_set&.compose&.include&.map do |include_element|
|
|
78
|
+
if include_element.concept.present?
|
|
79
|
+
include_element
|
|
80
|
+
elsif include_element.system.present? && include_element.filter&.empty?
|
|
81
|
+
# Cannot process intensional value set with filters
|
|
82
|
+
ig_resources.code_system_by_url(include_element.system)
|
|
83
|
+
elsif include_element.valueSet.present?
|
|
84
|
+
include_element.valueSet.map do |vs|
|
|
85
|
+
a_value_set = ig_resources.value_set_by_url(vs)
|
|
86
|
+
bound_systems_from_valueset(a_value_set)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end&.flatten&.compact
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def codes_from_value_set_binding(the_element)
|
|
93
|
+
codes_from_system_code_pair(codings_from_value_set_binding(the_element))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def codes_from_system_code_pair(codings)
|
|
97
|
+
codings.present? ? codings.map { |coding| coding[:code] }.compact.uniq : []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def codings_from_value_set_binding(the_element)
|
|
101
|
+
return [] if the_element.nil?
|
|
102
|
+
|
|
103
|
+
bound_systems = bound_systems(the_element)
|
|
104
|
+
|
|
105
|
+
return codings_from_bound_systems(bound_systems) if bound_systems.present?
|
|
106
|
+
|
|
107
|
+
expansion_contains = value_set_expansion_contains(the_element)
|
|
108
|
+
|
|
109
|
+
return [] if expansion_contains.blank?
|
|
110
|
+
|
|
111
|
+
expansion_contains.map { |contains| { system: contains.system, code: contains.code } }.compact.uniq
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def codings_from_bound_systems(bound_systems)
|
|
115
|
+
return [] unless bound_systems.present?
|
|
116
|
+
|
|
117
|
+
bound_systems.flat_map do |bound_system|
|
|
118
|
+
case bound_system
|
|
119
|
+
when FHIR::ValueSet::Compose::Include
|
|
120
|
+
bound_system.concept.map { |concept| { system: bound_system.system, code: concept.code } }
|
|
121
|
+
when FHIR::CodeSystem
|
|
122
|
+
bound_system.concept.map { |concept| { system: bound_system.url, code: concept.code } }
|
|
123
|
+
else
|
|
124
|
+
[]
|
|
125
|
+
end
|
|
126
|
+
end.uniq
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def value_set_expansion_contains(element)
|
|
130
|
+
value_set(element)&.expansion&.contains
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def fhir_metadata(current_path)
|
|
134
|
+
FHIR.const_get(resource)::METADATA[current_path]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def values_from_resource_metadata(paths)
|
|
138
|
+
values = []
|
|
139
|
+
|
|
140
|
+
paths.each do |current_path|
|
|
141
|
+
current_metadata = fhir_metadata(current_path)
|
|
142
|
+
|
|
143
|
+
next unless current_metadata&.dig('valid_codes').present?
|
|
144
|
+
|
|
145
|
+
values += current_metadata['valid_codes'].flat_map do |system, codes|
|
|
146
|
+
codes.map { |code| { system:, code: } }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
values
|
|
151
|
+
end
|
|
8
152
|
end
|
|
9
153
|
end
|
|
10
154
|
end
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative 'fhir_resource_navigation'
|
|
4
4
|
|
|
5
5
|
module USQualityCoreTestKit
|
|
6
6
|
module MustSupportTest
|
|
7
|
-
include
|
|
8
|
-
extend
|
|
7
|
+
include Inferno::DSL::FHIRResourceNavigation
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
def_delegators 'self.class', :metadata
|
|
11
|
+
|
|
12
|
+
def all_scratch_resources
|
|
13
|
+
scratch_resources[:all]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def perform_must_support_test(resources)
|
|
17
|
+
skip_if resources.blank?, "No #{resource_type} resources were found"
|
|
18
|
+
|
|
19
|
+
skip { assert_must_support_elements_present(resources, nil, metadata:) }
|
|
20
|
+
end
|
|
9
21
|
end
|
|
10
22
|
end
|
|
@@ -1,10 +1,105 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/read_test'
|
|
4
|
-
|
|
5
3
|
module USQualityCoreTestKit
|
|
6
4
|
module ReadTest
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
def all_scratch_resources
|
|
6
|
+
scratch_resources[:all] ||= []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def perform_read_test(resources, _reply_handler = nil, delayed_reference: false)
|
|
10
|
+
skip_if resources.blank?, no_resources_skip_message
|
|
11
|
+
|
|
12
|
+
resources_to_read = if delayed_reference
|
|
13
|
+
readable_references(resources)
|
|
14
|
+
else
|
|
15
|
+
readable_resources(resources)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
assert resources_to_read.present?, "No #{resource_type} id found."
|
|
19
|
+
|
|
20
|
+
if config.options[:read_all_resources]
|
|
21
|
+
if delayed_reference
|
|
22
|
+
all_referencing_resources = referencing_resources(resources_to_read)
|
|
23
|
+
info %(
|
|
24
|
+
The #{resource_type} references used for this test were pulled from the following resources:
|
|
25
|
+
#{all_referencing_resources}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
resources_to_read.map! { |resource| resource[:reference] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
resources_to_read.each do |resource|
|
|
32
|
+
read_and_validate(resource)
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
first_resource = resources_to_read.first
|
|
36
|
+
if delayed_reference.present?
|
|
37
|
+
info %(
|
|
38
|
+
The #{resource_type} reference used for this test was pulled from resource
|
|
39
|
+
#{first_resource[:referencing_resource]}
|
|
40
|
+
)
|
|
41
|
+
first_resource = first_resource[:reference]
|
|
42
|
+
end
|
|
43
|
+
read_and_validate(first_resource)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def referencing_resources(readable_resources)
|
|
48
|
+
readable_resources
|
|
49
|
+
.map { |resource| resource[:referencing_resource] }
|
|
50
|
+
.join(', ')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def readable_references(resources)
|
|
54
|
+
references = resources.filter_map do |resource|
|
|
55
|
+
next unless resource[:reference].present? && resource[:reference].is_a?(FHIR::Reference)
|
|
56
|
+
|
|
57
|
+
reference_id = resource[:reference].reference&.split('/')&.last
|
|
58
|
+
next unless reference_id&.present?
|
|
59
|
+
|
|
60
|
+
resource
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
references.uniq { |resource| resource[:reference].reference.split('/').last }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def readable_resources(resources)
|
|
67
|
+
resources
|
|
68
|
+
.select { |resource| resource.is_a?(resource_class) || resource.is_a?(FHIR::Reference) }
|
|
69
|
+
.select { |resource| (resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id).present? }
|
|
70
|
+
.compact
|
|
71
|
+
.uniq { |resource| resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def read_and_validate(resource_to_read)
|
|
75
|
+
id = resource_id(resource_to_read)
|
|
76
|
+
|
|
77
|
+
fhir_read resource_type, id
|
|
78
|
+
|
|
79
|
+
assert_response_status(200)
|
|
80
|
+
assert_resource_type(resource_type)
|
|
81
|
+
assert resource.id.present? && resource.id == id, bad_resource_id_message(id)
|
|
82
|
+
|
|
83
|
+
return unless resource_to_read.is_a? FHIR::Reference
|
|
84
|
+
|
|
85
|
+
all_scratch_resources << resource
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def resource_id(resource)
|
|
89
|
+
resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def no_resources_skip_message
|
|
93
|
+
"No #{resource_type} resources appear to be available. " \
|
|
94
|
+
'Please use patients with more information.'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bad_resource_id_message(expected_id)
|
|
98
|
+
"Expected resource to have id: `#{expected_id.inspect}`, but found `#{resource.id.inspect}`"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def resource_class
|
|
102
|
+
FHIR.const_get(resource_type)
|
|
103
|
+
end
|
|
9
104
|
end
|
|
10
105
|
end
|