us_quality_core_test_kit 0.1.0 → 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 -17
|
@@ -1,13 +1,64 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/read_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 ReadTestGenerator
|
|
8
|
+
class ReadTestGenerator
|
|
9
|
+
attr_accessor :group_metadata, :base_output_dir
|
|
10
|
+
|
|
11
|
+
def initialize(group_metadata, base_output_dir)
|
|
12
|
+
self.group_metadata = group_metadata
|
|
13
|
+
self.base_output_dir = base_output_dir
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def output
|
|
17
|
+
@output ||= ERB.new(template).result(binding)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def base_output_file_name
|
|
21
|
+
"#{class_name.underscore}.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output_file_directory
|
|
25
|
+
File.join(base_output_dir, profile_identifier)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output_file_name
|
|
29
|
+
File.join(output_file_directory, base_output_file_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def read_interaction
|
|
33
|
+
self.class.read_interaction(group_metadata)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def resource_type
|
|
37
|
+
group_metadata.resource
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resource_collection_string
|
|
41
|
+
if group_metadata.delayed? && resource_type != 'Provenance'
|
|
42
|
+
"scratch.dig(:references, '#{resource_type}'), delayed_reference: true"
|
|
43
|
+
else
|
|
44
|
+
'all_scratch_resources'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def conformance_expectation
|
|
49
|
+
read_interaction[:expectation]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def generate
|
|
53
|
+
FileUtils.mkdir_p(output_file_directory)
|
|
54
|
+
File.write(output_file_name, output)
|
|
55
|
+
|
|
56
|
+
group_metadata.add_test(
|
|
57
|
+
id: test_id,
|
|
58
|
+
file_name: base_output_file_name
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
11
62
|
class << self
|
|
12
63
|
def generate(ig_metadata, base_output_dir)
|
|
13
64
|
ig_metadata.groups
|
|
@@ -1,13 +1,67 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/reference_resolution_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 ReferenceResolutionTestGenerator
|
|
8
|
+
class ReferenceResolutionTestGenerator
|
|
9
|
+
attr_accessor :group_metadata, :base_output_dir
|
|
10
|
+
|
|
11
|
+
def initialize(group_metadata, base_output_dir)
|
|
12
|
+
self.group_metadata = group_metadata
|
|
13
|
+
self.base_output_dir = base_output_dir
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def output
|
|
17
|
+
@output ||= ERB.new(template, trim_mode: '-').result(binding)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def base_output_file_name
|
|
21
|
+
"#{class_name.underscore}.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output_file_directory
|
|
25
|
+
File.join(base_output_dir, profile_identifier)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output_file_name
|
|
29
|
+
File.join(output_file_directory, base_output_file_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def resource_type
|
|
33
|
+
group_metadata.resource
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def resource_collection_string
|
|
37
|
+
'scratch_resources[:all]'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def must_support_references
|
|
41
|
+
group_metadata.must_supports[:elements]
|
|
42
|
+
.select { |element| element[:types]&.include?('Reference') }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def must_support_reference_list_string
|
|
46
|
+
must_support_references
|
|
47
|
+
.map { |element| "#{' ' * 8}* #{resource_type}.#{element[:path]}" }
|
|
48
|
+
.uniq
|
|
49
|
+
.sort
|
|
50
|
+
.join("\n")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def generate
|
|
54
|
+
return if must_support_references.empty?
|
|
55
|
+
|
|
56
|
+
FileUtils.mkdir_p(output_file_directory)
|
|
57
|
+
File.write(output_file_name, output)
|
|
58
|
+
|
|
59
|
+
group_metadata.add_test(
|
|
60
|
+
id: test_id,
|
|
61
|
+
file_name: base_output_file_name
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
11
65
|
class << self
|
|
12
66
|
def generate(ig_metadata, base_output_dir)
|
|
13
67
|
ig_metadata.groups
|
|
@@ -1,13 +1,183 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/search_definition_metadata_extractor'
|
|
4
|
-
|
|
5
|
-
require_relative 'special_cases'
|
|
6
3
|
require_relative 'value_extractor'
|
|
4
|
+
require_relative 'special_cases'
|
|
7
5
|
|
|
8
6
|
module USQualityCoreTestKit
|
|
9
7
|
class Generator
|
|
10
|
-
class SearchDefinitionMetadataExtractor
|
|
8
|
+
class SearchDefinitionMetadataExtractor
|
|
9
|
+
attr_accessor :ig_resources, :name, :profile_elements, :group_metadata
|
|
10
|
+
|
|
11
|
+
def initialize(name, ig_resources, profile_elements, group_metadata)
|
|
12
|
+
self.name = name
|
|
13
|
+
self.ig_resources = ig_resources
|
|
14
|
+
self.profile_elements = profile_elements
|
|
15
|
+
self.group_metadata = group_metadata
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def search_definition
|
|
19
|
+
@search_definition ||=
|
|
20
|
+
{
|
|
21
|
+
paths: paths,
|
|
22
|
+
full_paths: full_paths,
|
|
23
|
+
comparators: comparators,
|
|
24
|
+
values: values,
|
|
25
|
+
type: type,
|
|
26
|
+
contains_multiple: contains_multiple?,
|
|
27
|
+
multiple_or: multiple_or_expectation,
|
|
28
|
+
chain: chain
|
|
29
|
+
}.compact
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def resource
|
|
33
|
+
group_metadata[:resource]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def param
|
|
37
|
+
@param ||= ig_resources.search_param_by_resource_and_name(resource, name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def param_hash
|
|
41
|
+
param.source_hash
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def full_paths
|
|
45
|
+
@full_paths ||=
|
|
46
|
+
begin
|
|
47
|
+
path = param.expression.gsub(/.where\(resolve\((.*)/, '').gsub('url = \'', 'url=\'')
|
|
48
|
+
path = path[1..-2] if path.start_with?('(') && path.end_with?(')')
|
|
49
|
+
path.scan(/[. ]as[( ]([^)]*)[)]?/).flatten.map do |as_type|
|
|
50
|
+
path.gsub!(/[. ]as[( ](#{as_type}[^)]*)[)]?/, as_type.upcase_first) if as_type.present?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
path.gsub!('Resource.', "#{resource}.") if path.start_with?('Resource.')
|
|
54
|
+
|
|
55
|
+
full_paths = path.split('|')
|
|
56
|
+
|
|
57
|
+
full_paths
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def remove_additional_extension_from_asserted_date(full_paths)
|
|
62
|
+
full_paths.each do |full_path|
|
|
63
|
+
next unless full_path.include?('http://hl7.org/fhir/StructureDefinition/condition-assertedDate')
|
|
64
|
+
|
|
65
|
+
full_path.gsub!(/\).extension./, ').')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def extension_definition
|
|
70
|
+
@extension_definition ||=
|
|
71
|
+
begin
|
|
72
|
+
ext_definition = nil
|
|
73
|
+
extensions&.each do |ext_metadata|
|
|
74
|
+
ext_definition = ig_resources.profile_by_url(ext_metadata[:url])
|
|
75
|
+
break if ext_definition.present?
|
|
76
|
+
end
|
|
77
|
+
ext_definition
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def comparator_expectation_extensions
|
|
82
|
+
@comparator_expectation_extensions ||= param_hash['_comparator'] || []
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def support_expectation(extension)
|
|
86
|
+
extension['extension'].first['valueCode']
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def comparator_expectation(extension)
|
|
90
|
+
if extension.nil?
|
|
91
|
+
'MAY'
|
|
92
|
+
else
|
|
93
|
+
support_expectation(extension)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def comparators
|
|
98
|
+
{}.tap do |comparators|
|
|
99
|
+
param.comparator&.each_with_index do |comparator, index|
|
|
100
|
+
comparators[comparator.to_sym] = comparator_expectation(comparator_expectation_extensions[index])
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def contains_multiple?
|
|
106
|
+
if profile_element.present?
|
|
107
|
+
if profile_element.id.start_with?('Extension') && extension_definition.present?
|
|
108
|
+
# Find the extension instance in a US Core profile
|
|
109
|
+
target_element = profile_elements.find do |element|
|
|
110
|
+
element.type.any? { |type| type.code == 'Extension' && type.profile.include?(extension_definition.url) }
|
|
111
|
+
end
|
|
112
|
+
target_element&.max == '*'
|
|
113
|
+
else
|
|
114
|
+
profile_element.max == '*'
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def chain_extensions
|
|
122
|
+
param_hash['_chain']
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def chain_expectations
|
|
126
|
+
chain_extensions.map { |extension| support_expectation(extension) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def chain
|
|
130
|
+
return nil if param.chain.blank?
|
|
131
|
+
|
|
132
|
+
param.chain
|
|
133
|
+
.zip(chain_expectations)
|
|
134
|
+
.map { |chain, expectation| { chain: chain, expectation: expectation } }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def multiple_or_expectation
|
|
138
|
+
param_hash['_multipleOr'] ? param_hash['_multipleOr']['extension'].first['valueCode'] : 'MAY'
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def values_from_must_supports(profile_element)
|
|
142
|
+
return if profile_element.nil?
|
|
143
|
+
|
|
144
|
+
short_path = profile_element.path.split('.', 2)[1]
|
|
145
|
+
|
|
146
|
+
values_from_must_support_slices(profile_element, short_path, true).presence ||
|
|
147
|
+
values_from_must_support_slices(profile_element, short_path, false).presence ||
|
|
148
|
+
values_from_must_support_elements(short_path).presence ||
|
|
149
|
+
[]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def values_from_must_support_slices(profile_element, short_path, mandatory_slice_only)
|
|
153
|
+
group_metadata[:must_supports][:slices]
|
|
154
|
+
.select { |slice| [short_path, "#{short_path}.coding"].include?(slice[:path]) }
|
|
155
|
+
.map do |slice|
|
|
156
|
+
slice_element = profile_elements.find { |element| slice[:slice_id] == element.id }
|
|
157
|
+
next if profile_element.min.positive? && slice_element.min.zero? && mandatory_slice_only
|
|
158
|
+
|
|
159
|
+
case slice[:discriminator][:type]
|
|
160
|
+
when 'patternCoding', 'patternCodeableConcept'
|
|
161
|
+
slice[:discriminator][:code]
|
|
162
|
+
when 'requiredBinding'
|
|
163
|
+
value_extractor.codes_from_system_code_pair(slice[:discriminator][:values])
|
|
164
|
+
when 'value'
|
|
165
|
+
slice[:discriminator][:values]
|
|
166
|
+
.select { |value| value[:path] == 'coding.code' }
|
|
167
|
+
.map { |value| value[:value] }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
.compact.flatten
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def values_from_resource_metadata(paths)
|
|
174
|
+
if multiple_or_expectation == 'SHALL' || paths.any? { |path| path.downcase.include?('status') }
|
|
175
|
+
value_extractor.codes_from_system_code_pair(value_extractor.values_from_resource_metadata(paths))
|
|
176
|
+
else
|
|
177
|
+
[]
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
11
181
|
EXTENSION_URL_REGEX = /(?:modifierExtension|extension)\.where\(url='([^']+)'\)/
|
|
12
182
|
EXTENSION_VALUE_PATH_REGEX = /\.(?:value(?:[A-Z]\w*|\[x\])?)\z/
|
|
13
183
|
|
|
@@ -71,10 +241,16 @@ module USQualityCoreTestKit
|
|
|
71
241
|
category_search = paths.any? { |path| path.split('.').first == 'category' }
|
|
72
242
|
fixed_boolean = profile_element.fixedBoolean if profile_element.respond_to?(:fixedBoolean)
|
|
73
243
|
fixed_boolean_value = [fixed_boolean.to_s] unless fixed_boolean.nil?
|
|
244
|
+
base_values =
|
|
245
|
+
values_from_must_supports(profile_element).presence ||
|
|
246
|
+
value_extractor.values_from_fixed_codes(profile_element, type).presence ||
|
|
247
|
+
value_extractor.codes_from_value_set_binding(profile_element).presence ||
|
|
248
|
+
values_from_resource_metadata(paths).presence ||
|
|
249
|
+
[]
|
|
74
250
|
|
|
75
251
|
fixed_boolean_value.presence ||
|
|
76
252
|
(SpecialCases::PROFILE_CATEGORY_SEARCH_VALUES[group_metadata[:profile_url]] if category_search).presence ||
|
|
77
|
-
|
|
253
|
+
base_values.presence ||
|
|
78
254
|
(value_extractor.codes_from_system_code_pair(value_extractor.values_from_resource_metadata(paths)) if category_search).presence ||
|
|
79
255
|
[]
|
|
80
256
|
end
|
|
@@ -1,12 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/search_metadata_extractor'
|
|
4
|
-
|
|
5
3
|
require_relative 'search_definition_metadata_extractor'
|
|
6
4
|
|
|
7
5
|
module USQualityCoreTestKit
|
|
8
6
|
class Generator
|
|
9
|
-
class SearchMetadataExtractor
|
|
7
|
+
class SearchMetadataExtractor
|
|
8
|
+
COMBO_EXTENSION_URL =
|
|
9
|
+
'http://hl7.org/fhir/StructureDefinition/capabilitystatement-search-parameter-combination'
|
|
10
|
+
|
|
11
|
+
attr_accessor :resource_capabilities, :ig_resources, :profile_elements, :group_metadata
|
|
12
|
+
|
|
13
|
+
def initialize(resource_capabilities, ig_resources, profile_elements, group_metadata)
|
|
14
|
+
self.resource_capabilities = resource_capabilities
|
|
15
|
+
self.ig_resources = ig_resources
|
|
16
|
+
self.profile_elements = profile_elements
|
|
17
|
+
self.group_metadata = group_metadata
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def searches
|
|
21
|
+
@searches ||= basic_searches + combo_searches
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def conformance_expectation(search_param)
|
|
25
|
+
search_param.extension.first.valueCode # TODO: fix expectation extension finding
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def no_search_params?
|
|
29
|
+
resource_capabilities.searchParam.blank?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def basic_searches
|
|
33
|
+
return [] if no_search_params?
|
|
34
|
+
|
|
35
|
+
resource_capabilities.searchParam
|
|
36
|
+
.select { |search_param| %w[SHALL SHOULD].include? conformance_expectation(search_param) }
|
|
37
|
+
.map do |search_param|
|
|
38
|
+
{
|
|
39
|
+
names: [search_param.name],
|
|
40
|
+
expectation: conformance_expectation(search_param)
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def search_extensions
|
|
46
|
+
resource_capabilities.extension
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def combo_searches
|
|
50
|
+
return [] if search_extensions.blank?
|
|
51
|
+
|
|
52
|
+
search_extensions
|
|
53
|
+
.select { |extension| extension.url == COMBO_EXTENSION_URL }
|
|
54
|
+
.select { |extension| %w[SHALL SHOULD].include? conformance_expectation(extension) }
|
|
55
|
+
.map do |extension|
|
|
56
|
+
names = extension.extension.select { |param| param.valueString.present? }.map(&:valueString)
|
|
57
|
+
{
|
|
58
|
+
expectation: conformance_expectation(extension),
|
|
59
|
+
names: names
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def search_param_names
|
|
65
|
+
searches.flat_map { |search| search[:names] }.uniq
|
|
66
|
+
end
|
|
67
|
+
|
|
10
68
|
def search_definitions
|
|
11
69
|
search_param_names.each_with_object({}) do |name, definitions|
|
|
12
70
|
definitions[name.to_sym] =
|
|
@@ -1,13 +1,196 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/search_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 SearchTestGenerator
|
|
8
|
+
class SearchTestGenerator
|
|
9
|
+
attr_accessor :group_metadata, :search_metadata, :base_output_dir
|
|
10
|
+
|
|
11
|
+
def initialize(group_metadata, search_metadata, base_output_dir)
|
|
12
|
+
self.group_metadata = group_metadata
|
|
13
|
+
self.search_metadata = search_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, profile_identifier)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def output_file_name
|
|
30
|
+
File.join(output_file_directory, base_output_file_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def search_identifier
|
|
34
|
+
search_metadata[:names].join('_').tr('-', '_')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def search_title
|
|
38
|
+
search_identifier.camelize
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resource_type
|
|
42
|
+
group_metadata.resource
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def conformance_expectation
|
|
46
|
+
search_metadata[:expectation]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def search_params
|
|
50
|
+
@search_params ||=
|
|
51
|
+
search_metadata[:names].map do |name|
|
|
52
|
+
{
|
|
53
|
+
name: name,
|
|
54
|
+
path: search_definition(name)[:path]
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def first_search?
|
|
60
|
+
group_metadata.searches.first == search_metadata
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fixed_value_search_param_name
|
|
64
|
+
(search_metadata[:names] - [:patient]).first
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def search_param_name_string
|
|
68
|
+
search_metadata[:names].join(' + ')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def search_param_names
|
|
72
|
+
search_params.map { |param| param[:name] }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def search_param_names_array
|
|
76
|
+
array_of_strings(search_param_names)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def path_for_value(path)
|
|
80
|
+
path == 'class' ? 'local_class' : path
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def required_comparators_for_param(name)
|
|
84
|
+
search_definition(name)[:comparators].select { |_comparator, expectation| expectation == 'SHALL' }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def required_comparators
|
|
88
|
+
@required_comparators ||=
|
|
89
|
+
search_param_names.each_with_object({}) do |name, comparators|
|
|
90
|
+
required_comparators = required_comparators_for_param(name)
|
|
91
|
+
comparators[name] = required_comparators if required_comparators.present?
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def optional?
|
|
96
|
+
conformance_expectation != 'SHALL' || !search_metadata[:must_support_or_mandatory]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def search_definition(name)
|
|
100
|
+
group_metadata.search_definitions[name.to_sym]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def saves_delayed_references?
|
|
104
|
+
first_search? && group_metadata.delayed_references.present?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def possible_status_search?
|
|
108
|
+
search_metadata[:names].none? { |name| name.include? 'status' } &&
|
|
109
|
+
group_metadata.search_definitions.keys.any? { |key| key.to_s.include? 'status' }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def token_search_params
|
|
113
|
+
@token_search_params ||=
|
|
114
|
+
search_param_names.select do |name|
|
|
115
|
+
%w[Identifier CodeableConcept Coding].include? group_metadata.search_definitions[name.to_sym][:type]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def token_search_params_string
|
|
120
|
+
array_of_strings(token_search_params)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def required_multiple_or_search_params
|
|
124
|
+
@required_multiple_or_search_params ||=
|
|
125
|
+
search_param_names.select do |name|
|
|
126
|
+
search_definition(name)[:multiple_or] == 'SHALL'
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def required_multiple_or_search_params_string
|
|
131
|
+
array_of_strings(required_multiple_or_search_params)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def required_comparators_string
|
|
135
|
+
array_of_strings(required_comparators.keys)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def array_of_strings(array)
|
|
139
|
+
quoted_strings = array.map { |element| "'#{element}'" }
|
|
140
|
+
"[#{quoted_strings.join(', ')}]"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_reference_variants?
|
|
144
|
+
first_search? && search_param_names.include?('patient')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_post_search?
|
|
148
|
+
first_search?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def search_properties
|
|
152
|
+
{}.tap do |properties|
|
|
153
|
+
properties[:first_search] = 'true' if first_search?
|
|
154
|
+
properties[:fixed_value_search] = 'true' if fixed_value_search?
|
|
155
|
+
properties[:resource_type] = "'#{resource_type}'"
|
|
156
|
+
properties[:search_param_names] = search_param_names_array
|
|
157
|
+
properties[:saves_delayed_references] = 'true' if saves_delayed_references?
|
|
158
|
+
properties[:possible_status_search] = 'true' if possible_status_search?
|
|
159
|
+
properties[:test_medication_inclusion] = 'true' if test_medication_inclusion?
|
|
160
|
+
properties[:token_search_params] = token_search_params_string if token_search_params.present?
|
|
161
|
+
properties[:test_reference_variants] = 'true' if test_reference_variants?
|
|
162
|
+
properties[:params_with_comparators] = required_comparators_string if required_comparators.present?
|
|
163
|
+
properties[:multiple_or_search_params] = required_multiple_or_search_params_string if required_multiple_or_search_params.present?
|
|
164
|
+
properties[:test_post_search] = 'true' if first_search?
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def search_test_properties_string
|
|
169
|
+
search_properties
|
|
170
|
+
.map { |key, value| "#{' ' * 8}#{key}: #{value}" }
|
|
171
|
+
.join(",\n")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def generate
|
|
175
|
+
FileUtils.mkdir_p(output_file_directory)
|
|
176
|
+
File.write(output_file_name, output)
|
|
177
|
+
|
|
178
|
+
group_metadata.add_test(
|
|
179
|
+
id: test_id,
|
|
180
|
+
file_name: base_output_file_name
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def medication_inclusion_description
|
|
185
|
+
return '' unless test_medication_inclusion?
|
|
186
|
+
|
|
187
|
+
<<~MEDICATION_INCLUSION_DESCRIPTION
|
|
188
|
+
If any #{resource_type} resources use external references to
|
|
189
|
+
Medications, the search will be repeated with
|
|
190
|
+
`_include=#{resource_type}:medication`.
|
|
191
|
+
MEDICATION_INCLUSION_DESCRIPTION
|
|
192
|
+
end
|
|
193
|
+
|
|
11
194
|
class << self
|
|
12
195
|
def generate(ig_metadata, base_output_dir)
|
|
13
196
|
ig_metadata.groups
|
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/suite_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 SuiteGenerator
|
|
8
|
+
class SuiteGenerator
|
|
9
|
+
class << self
|
|
10
|
+
def generate(ig_metadata, base_output_dir)
|
|
11
|
+
new(ig_metadata, base_output_dir).generate
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_accessor :ig_metadata, :base_output_dir
|
|
16
|
+
|
|
17
|
+
def initialize(ig_metadata, base_output_dir)
|
|
18
|
+
self.ig_metadata = ig_metadata
|
|
19
|
+
self.base_output_dir = base_output_dir
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def output
|
|
23
|
+
@output ||= ERB.new(template, trim_mode: '-').result(binding)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def output_file_name
|
|
27
|
+
File.join(base_output_dir, base_output_file_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate
|
|
31
|
+
File.write(output_file_name, output)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def group_file_list
|
|
35
|
+
@group_file_list ||=
|
|
36
|
+
groups.map { |group| group.file_name.delete_suffix('.rb') }
|
|
37
|
+
end
|
|
38
|
+
|
|
11
39
|
def version_specific_message_filters
|
|
12
40
|
[
|
|
13
|
-
# Patient
|
|
14
|
-
# validator
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# This extension is correct but is not yet correctly passing in the validator
|
|
41
|
+
# Patient extension messages are suppressed because the CQL package can load US Core 7 into
|
|
42
|
+
# the validator session while US Quality Core 0.5.0 uses US Core 6.1.0.
|
|
43
|
+
/\bPatient\.extension/,
|
|
44
|
+
# This extension is correct but is not yet correctly passing in the validator.
|
|
18
45
|
%r{DeviceRequest.*DeviceRequest\.modifierExtension\[\d+\]:\s*Slicing cannot be evaluated:\s*Unable to resolve profile CanonicalType\[http://hl7\.org/fhir/5\.0/StructureDefinition/extension-DeviceRequest\.doNotPerform\]}
|
|
19
46
|
]
|
|
20
47
|
end
|