davinci_us_drug_formulary_test_kit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/conformance_support_test.rb +41 -0
  4. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/fhir_version_test.rb +15 -0
  5. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/instantiate_test.rb +19 -0
  6. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/json_support_test.rb +40 -0
  7. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/profile_support_test.rb +39 -0
  8. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/v2.0.1/capability_statement_group.rb +78 -0
  9. data/lib/davinci_us_drug_formulary_test_kit/date_search_validation.rb +121 -0
  10. data/lib/davinci_us_drug_formulary_test_kit/fhir_resource_navigation.rb +155 -0
  11. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_code_search_test.rb +54 -0
  12. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_drug_tier_search_test.rb +43 -0
  13. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_formulary_include_search_test.rb +40 -0
  14. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_formulary_search_test.rb +43 -0
  15. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_id_search_test.rb +42 -0
  16. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_lastupdated_search_test.rb +42 -0
  17. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_must_support_test.rb +46 -0
  18. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_period_search_test.rb +42 -0
  19. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_pharmacy_benefit_type_search_test.rb +43 -0
  20. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_read_test.rb +26 -0
  21. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_reference_resolution_test.rb +40 -0
  22. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_status_search_test.rb +42 -0
  23. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_subject_include_search_test.rb +40 -0
  24. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_subject_search_test.rb +43 -0
  25. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_validation_test.rb +39 -0
  26. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/metadata.yml +292 -0
  27. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic_group.rb +107 -0
  28. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_coverage_area_search_test.rb +45 -0
  29. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_coverage_type_search_test.rb +45 -0
  30. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_formulary_coverage_search_test.rb +45 -0
  31. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_id_search_test.rb +42 -0
  32. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_identifier_search_test.rb +43 -0
  33. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_lastupdated_search_test.rb +42 -0
  34. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_must_support_test.rb +42 -0
  35. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_name_search_test.rb +42 -0
  36. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_period_search_test.rb +42 -0
  37. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_read_test.rb +26 -0
  38. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_status_search_test.rb +53 -0
  39. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_type_search_test.rb +43 -0
  40. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_validation_test.rb +39 -0
  41. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/metadata.yml +278 -0
  42. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary_group.rb +104 -0
  43. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_city_search_test.rb +44 -0
  44. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_postalcode_search_test.rb +44 -0
  45. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_search_test.rb +42 -0
  46. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_state_search_test.rb +44 -0
  47. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_id_search_test.rb +56 -0
  48. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_lastupdated_search_test.rb +44 -0
  49. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_must_support_test.rb +37 -0
  50. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_read_test.rb +26 -0
  51. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_validation_test.rb +39 -0
  52. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/metadata.yml +177 -0
  53. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location_group.rb +92 -0
  54. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_code_search_test.rb +43 -0
  55. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_doseform_search_test.rb +45 -0
  56. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_drug_name_search_test.rb +44 -0
  57. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_id_search_test.rb +42 -0
  58. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_lastupdated_search_test.rb +42 -0
  59. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_must_support_test.rb +47 -0
  60. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_read_test.rb +26 -0
  61. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_status_search_test.rb +53 -0
  62. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_validation_test.rb +39 -0
  63. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/metadata.yml +214 -0
  64. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge_group.rb +91 -0
  65. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/metadata.yml +1192 -0
  66. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/metadata.yml +371 -0
  67. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_coverage_area_search_test.rb +43 -0
  68. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_coverage_type_search_test.rb +43 -0
  69. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_formulary_coverage_include_search_test.rb +40 -0
  70. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_formulary_coverage_search_test.rb +43 -0
  71. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_id_search_test.rb +42 -0
  72. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_identifier_search_test.rb +43 -0
  73. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_lastupdated_search_test.rb +42 -0
  74. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_must_support_test.rb +66 -0
  75. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_name_search_test.rb +42 -0
  76. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_period_search_test.rb +42 -0
  77. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_read_test.rb +26 -0
  78. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_reference_resolution_test.rb +40 -0
  79. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_status_search_test.rb +53 -0
  80. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_type_search_test.rb +43 -0
  81. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_validation_test.rb +39 -0
  82. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan_group.rb +108 -0
  83. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/usdf_test_suite.rb +114 -0
  84. data/lib/davinci_us_drug_formulary_test_kit/generator/group_generator.rb +181 -0
  85. data/lib/davinci_us_drug_formulary_test_kit/generator/group_metadata.rb +79 -0
  86. data/lib/davinci_us_drug_formulary_test_kit/generator/group_metadata_extractor.rb +329 -0
  87. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_loader.rb +77 -0
  88. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_metadata.rb +33 -0
  89. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_metadata_extractor.rb +40 -0
  90. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_resources.rb +60 -0
  91. data/lib/davinci_us_drug_formulary_test_kit/generator/include_search_test_generator.rb +68 -0
  92. data/lib/davinci_us_drug_formulary_test_kit/generator/must_support_metadata_extractor.rb +384 -0
  93. data/lib/davinci_us_drug_formulary_test_kit/generator/must_support_test_generator.rb +117 -0
  94. data/lib/davinci_us_drug_formulary_test_kit/generator/naming.rb +28 -0
  95. data/lib/davinci_us_drug_formulary_test_kit/generator/read_test_generator.rb +92 -0
  96. data/lib/davinci_us_drug_formulary_test_kit/generator/reference_resolution_test_generator.rb +91 -0
  97. data/lib/davinci_us_drug_formulary_test_kit/generator/search_definition_metadata_extractor.rb +187 -0
  98. data/lib/davinci_us_drug_formulary_test_kit/generator/search_metadata_extractor.rb +59 -0
  99. data/lib/davinci_us_drug_formulary_test_kit/generator/search_test_generator.rb +270 -0
  100. data/lib/davinci_us_drug_formulary_test_kit/generator/suite_generator.rb +94 -0
  101. data/lib/davinci_us_drug_formulary_test_kit/generator/terminology_binding_metadata_extractor.rb +116 -0
  102. data/lib/davinci_us_drug_formulary_test_kit/generator/validation_test_generator.rb +102 -0
  103. data/lib/davinci_us_drug_formulary_test_kit/generator/value_extractor.rb +113 -0
  104. data/lib/davinci_us_drug_formulary_test_kit/generator.rb +94 -0
  105. data/lib/davinci_us_drug_formulary_test_kit/igs/package/r4_search-parameters.json +65408 -0
  106. data/lib/davinci_us_drug_formulary_test_kit/igs/package.tgz +0 -0
  107. data/lib/davinci_us_drug_formulary_test_kit/must_support_test.rb +224 -0
  108. data/lib/davinci_us_drug_formulary_test_kit/read_test.rb +62 -0
  109. data/lib/davinci_us_drug_formulary_test_kit/reference_resolution_test.rb +174 -0
  110. data/lib/davinci_us_drug_formulary_test_kit/request_logger.rb +46 -0
  111. data/lib/davinci_us_drug_formulary_test_kit/search_test.rb +767 -0
  112. data/lib/davinci_us_drug_formulary_test_kit/search_test_properties.rb +58 -0
  113. data/lib/davinci_us_drug_formulary_test_kit/validation_test.rb +56 -0
  114. data/lib/davinci_us_drug_formulary_test_kit/version.rb +5 -0
  115. data/lib/davinci_us_drug_formulary_test_kit.rb +1 -0
  116. metadata +245 -0
@@ -0,0 +1,224 @@
1
+ require_relative 'fhir_resource_navigation'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ module MustSupportTest
5
+ extend Forwardable
6
+ include FHIRResourceNavigation
7
+
8
+ def_delegators 'self.class', :metadata
9
+
10
+ def all_scratch_resources
11
+ scratch_resources[:all]
12
+ end
13
+
14
+ def perform_must_support_test(resources)
15
+ skip_if resources.blank?, "No #{resource_type} resources were found"
16
+
17
+ missing_elements(resources)
18
+ missing_slices(resources)
19
+ missing_extensions(resources)
20
+ handle_must_support_choices if metadata.must_supports[:choices].present?
21
+ # TODO: long term fix = allow intensional VS to be used for slicing in formulary drug
22
+ # ticket fi-2099
23
+ if resource_type == 'MedicationKnowledge'
24
+ # skip slice check for drugs
25
+ missing_elements.delete_if { |element| element[:path].include?('coding:') }
26
+ pass if (missing_elements + missing_extensions).empty?
27
+ else
28
+ missing_slices(resources)
29
+ pass if (missing_elements + missing_slices + missing_extensions).empty?
30
+ end
31
+
32
+ skip "Could not find #{missing_must_support_strings.join(', ')} in the #{resources.length} " \
33
+ "provided #{resource_type} resource(s)"
34
+ end
35
+
36
+ def handle_must_support_choices
37
+ missing_elements.delete_if do |element|
38
+ choices = metadata.must_supports[:choices].find { |choice| choice[:paths]&.include?(element[:path]) }
39
+ is_any_choice_supported?(choices)
40
+ end
41
+
42
+ missing_extensions.delete_if do |extension|
43
+ choices = metadata.must_supports[:choices].find { |choice| choice[:extension_ids]&.include?(extension[:id]) }
44
+ is_any_choice_supported?(choices)
45
+ end
46
+
47
+ missing_slices.delete_if do |slice|
48
+ choices = metadata.must_supports[:choices].find { |choice| choice[:slice_names]&.include?(slice[:slice_id]) }
49
+ is_any_choice_supported?(choices)
50
+ end
51
+ end
52
+
53
+ def is_any_choice_supported?(choices)
54
+ choices.present? &&
55
+ (
56
+ choices[:paths]&.any? { |path| missing_elements.none? { |element| element[:path] == path } } ||
57
+ choices[:extension_ids]&.any? do |extension_id|
58
+ missing_extensions.none? do |extension|
59
+ extension[:id] == extension_id
60
+ end
61
+ end ||
62
+ choices[:slice_names]&.any? { |slice_name| missing_slices.none? { |slice| slice[:slice_id] == slice_name } }
63
+ )
64
+ end
65
+
66
+ def missing_must_support_strings
67
+ missing_elements.map { |element_definition| missing_element_string(element_definition) } +
68
+ missing_slices.map { |slice_definition| slice_definition[:slice_id] } +
69
+ missing_extensions.map { |extension_definition| extension_definition[:id] }
70
+ end
71
+
72
+ def missing_element_string(element_definition)
73
+ if element_definition[:fixed_value].present?
74
+ "#{element_definition[:path]}:#{element_definition[:fixed_value]}"
75
+ else
76
+ element_definition[:path]
77
+ end
78
+ end
79
+
80
+ def exclude_uscdi_only_test?
81
+ config.options[:exclude_uscdi_only_test] == true
82
+ end
83
+
84
+ def must_support_extensions
85
+ if exclude_uscdi_only_test?
86
+ metadata.must_supports[:extensions].reject { |extension| extension[:uscdi_only] }
87
+ else
88
+ metadata.must_supports[:extensions]
89
+ end
90
+ end
91
+
92
+ def missing_extensions(resources = [])
93
+ @missing_extensions ||=
94
+ must_support_extensions.select do |extension_definition|
95
+ resources.none? do |resource|
96
+ path = extension_definition[:path]
97
+ if extension_definition[:path] != "extension"
98
+ extension = find_a_value_at(resource, path)
99
+ extension&.url == extension_definition[:url]
100
+ else
101
+ resource.extension.any? { |extension| extension.url == extension_definition[:url] }
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def must_support_elements
108
+ if exclude_uscdi_only_test?
109
+ metadata.must_supports[:elements].reject { |element| element[:uscdi_only] }
110
+ else
111
+ metadata.must_supports[:elements]
112
+ end
113
+ end
114
+
115
+ def missing_elements(resources = [])
116
+ @missing_elements ||=
117
+ must_support_elements.select do |element_definition|
118
+ resources.none? do |resource|
119
+ path = element_definition[:path] # .delete_suffix('[x]')
120
+ value_found = find_a_value_at(resource, path) do |value|
121
+ value_without_extensions =
122
+ value.respond_to?(:to_hash) ? value.to_hash.except('extension') : value
123
+
124
+ (value_without_extensions.present? || value_without_extensions == false) &&
125
+ (element_definition[:fixed_value].blank? || value == element_definition[:fixed_value])
126
+ end
127
+ # Note that false.present? => false, which is why we need to add this extra check
128
+ value_found.present? || value_found == false
129
+ end
130
+ end
131
+ @missing_elements
132
+ end
133
+
134
+ def must_support_slices
135
+ if exclude_uscdi_only_test?
136
+ metadata.must_supports[:slices].reject { |slice| slice[:uscdi_only] }
137
+ else
138
+ metadata.must_supports[:slices]
139
+ end
140
+ end
141
+
142
+ def missing_slices(resources = [])
143
+ @missing_slices ||=
144
+ must_support_slices.select do |slice|
145
+ resources.none? do |resource|
146
+ path = slice[:path] # .delete_suffix('[x]')
147
+ find_slice(resource, path, slice[:discriminator]).present?
148
+ end
149
+ end
150
+ end
151
+
152
+ def find_slice(resource, path, discriminator)
153
+ find_a_value_at(resource, path) do |element|
154
+ case discriminator[:type]
155
+ when 'patternCodeableConcept'
156
+ coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
157
+ find_a_value_at(element, coding_path) do |coding|
158
+ coding.code == discriminator[:code] && coding.system == discriminator[:system]
159
+ end
160
+ when 'patternCoding'
161
+ coding_path = discriminator[:path].present? ? discriminator[:path] : ''
162
+ find_a_value_at(element, coding_path) do |coding|
163
+ coding.code == discriminator[:code] && coding.system == discriminator[:system]
164
+ end
165
+ when 'patternIdentifier'
166
+ find_a_value_at(element, discriminator[:path]) { |identifier| identifier.system == discriminator[:system] }
167
+ when 'value'
168
+ values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
169
+ find_slice_by_values(element, values)
170
+ when 'type'
171
+ case discriminator[:code]
172
+ when 'Date'
173
+ begin
174
+ Date.parse(element)
175
+ rescue ArgumentError
176
+ false
177
+ end
178
+ when 'DateTime'
179
+ begin
180
+ DateTime.parse(element)
181
+ rescue ArgumentError
182
+ false
183
+ end
184
+ when 'String'
185
+ element.is_a? String
186
+ else
187
+ element.is_a? FHIR.const_get(discriminator[:code])
188
+ end
189
+ when 'requiredBinding'
190
+ coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
191
+ find_a_value_at(element, coding_path) { |coding| discriminator[:values].include?(coding.code) }
192
+ end
193
+ end
194
+ end
195
+
196
+ def find_slice_by_values(element, value_definitions)
197
+ path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
198
+ Array.wrap(element).find do |el|
199
+ path_prefixes.all? do |path_prefix|
200
+ value_definitions_for_path =
201
+ value_definitions
202
+ .select { |value_definition| value_definition[:path].first == path_prefix }
203
+ .each { |value_definition| value_definition[:path].shift }
204
+ find_a_value_at(el, path_prefix) do |el_found|
205
+ child_element_value_definitions, current_element_value_definitions =
206
+ value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }
207
+ current_element_values_match =
208
+ current_element_value_definitions
209
+ .all? { |value_definition| value_definition[:value] == el_found }
210
+
211
+ child_element_values_match =
212
+ if child_element_value_definitions.present?
213
+ find_slice_by_values(el_found, child_element_value_definitions)
214
+ else
215
+ true
216
+ end
217
+
218
+ current_element_values_match && child_element_values_match
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,62 @@
1
+ module DaVinciUSDrugFormularyTestKit
2
+ module ReadTest
3
+ def all_scratch_resources
4
+ scratch_resources[:all] ||= []
5
+ end
6
+
7
+ def perform_read_test(resources, _reply_handler = nil)
8
+ skip_if resources.blank?, no_resources_skip_message
9
+
10
+ resources_to_read = readable_resources(resources)
11
+
12
+ assert resources_to_read.present?, "No #{resource_type} id found."
13
+
14
+ if config.options[:read_all_resources]
15
+ resources_to_read.each do |resource|
16
+ read_and_validate(resource)
17
+ end
18
+ else
19
+ read_and_validate(resources_to_read.first)
20
+ end
21
+ end
22
+
23
+ def readable_resources(resources)
24
+ resources
25
+ .select { |resource| resource.is_a?(resource_class) || resource.is_a?(FHIR::Reference) }
26
+ .select { |resource| (resource.is_a?(FHIR::Reference) ? resource.reference_id : resource.id).present? }
27
+ .compact
28
+ .uniq { |resource| resource.is_a?(FHIR::Reference) ? resource.reference_id : resource.id }
29
+ end
30
+
31
+ def read_and_validate(resource_to_read)
32
+ id = resource_id(resource_to_read)
33
+
34
+ fhir_read resource_type, id
35
+
36
+ assert_response_status(200)
37
+ assert_resource_type(resource_type)
38
+ assert resource.id.present? && resource.id == id, bad_resource_id_message(id)
39
+
40
+ return unless resource_to_read.is_a? FHIR::Reference
41
+
42
+ all_scratch_resources << resource
43
+ end
44
+
45
+ def resource_id(resource)
46
+ resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id
47
+ end
48
+
49
+ def no_resources_skip_message
50
+ "No #{resource_type} resources appear to be available. " \
51
+ 'Please use patients with more information.'
52
+ end
53
+
54
+ def bad_resource_id_message(expected_id)
55
+ "Expected resource to have id: `#{expected_id.inspect}`, but found `#{resource.id.inspect}`"
56
+ end
57
+
58
+ def resource_class
59
+ FHIR.const_get(resource_type)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,174 @@
1
+ require_relative 'fhir_resource_navigation'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ module ReferenceResolutionTest
5
+ extend Forwardable
6
+ include FHIRResourceNavigation
7
+
8
+ def_delegators 'self.class', :metadata
9
+
10
+ def perform_reference_resolution_test(resources)
11
+ skip_if resources.blank?, no_resources_skip_message
12
+
13
+ pass if unresolved_references(resources).empty?
14
+
15
+ skip "Could not resolve Must Support references #{unresolved_references_strings.join(', ')}"
16
+ end
17
+
18
+ def unresolved_references_strings
19
+ unresolved_reference_hash =
20
+ unresolved_references.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |missing, hash|
21
+ hash[missing[:path]] << missing[:target_profile]
22
+ end
23
+ unresolved_reference_hash.map do |path, profiles|
24
+ "#{path}#{"(#{profiles.join('|')})" unless profiles.first.empty?}"
25
+ end
26
+ end
27
+
28
+ def record_resolved_reference(reference, target_profile)
29
+ saved_reference = resolved_references.find { |item| item[:reference] == reference.reference }
30
+
31
+ if saved_reference.present?
32
+ if target_profile.present? && !saved_reference[:profiles].include?(target_profile)
33
+ saved_reference[:profiles] << target_profile
34
+ end
35
+ else
36
+ saved_reference = {
37
+ reference: reference.reference,
38
+ profiles: []
39
+ }
40
+
41
+ saved_reference[:profiles] << target_profile if target_profile.present?
42
+ resolved_references.add(saved_reference)
43
+ end
44
+ end
45
+
46
+ def is_reference_resolved?(reference, target_profile)
47
+ resolved_references.any? do |item|
48
+ item[:reference] == reference.reference &&
49
+ (
50
+ target_profile.blank? || item[:profiles].include?(target_profile)
51
+ )
52
+ end
53
+ end
54
+
55
+ def resolved_references
56
+ scratch[:resolved_references] ||= Set.new
57
+ end
58
+
59
+ def no_resources_skip_message
60
+ "No #{resource_type} resources appear to be available. " \
61
+ 'Please use patients with more information.'
62
+ end
63
+
64
+ def must_support_references
65
+ metadata.must_supports[:references]
66
+ end
67
+
68
+ def unresolved_references(resources = [])
69
+ @unresolved_references ||=
70
+ must_support_references.select do |reference_path_profile_pair|
71
+ path = reference_path_profile_pair[:path]
72
+ target_profile = reference_path_profile_pair[:target_profile]
73
+
74
+ found_one_reference = false
75
+
76
+ resolve_one_reference = resources.any? do |resource|
77
+ value_found = resolve_path(resource, path)
78
+ next if value_found.empty?
79
+
80
+ found_one_reference = true
81
+
82
+ value_found.any? do |reference|
83
+ validate_reference_resolution(resource, reference, target_profile)
84
+ end
85
+ end
86
+
87
+ found_one_reference && !resolve_one_reference
88
+ end
89
+
90
+ if metadata.must_supports[:choices].present?
91
+ @unresolved_references.delete_if do |reference|
92
+ choice_profiles = metadata.must_supports[:choices].find do |choice|
93
+ choice[:target_profiles]&.include?(reference[:target_profile])
94
+ end
95
+
96
+ choice_profiles.present? &&
97
+ choice_profiles[:target_profiles]&.any? do |profile|
98
+ @unresolved_references.none? do |element|
99
+ element[:target_profile] == profile
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ @unresolved_references
106
+ end
107
+
108
+ def validate_reference_resolution(resource, reference, target_profile)
109
+ return true if is_reference_resolved?(reference, target_profile)
110
+
111
+ if reference.contained?
112
+ # if reference_id is blank it is referring to itself, so we know it exists
113
+ return true if reference.reference_id.blank?
114
+
115
+ return resource.contained.any? do |contained_resource|
116
+ contained_resource&.id == reference.reference_id &&
117
+ resource_is_valid_with_target_profile?(contained_resource, target_profile)
118
+ end
119
+ end
120
+
121
+ reference_type = reference.resource_type
122
+ reference_id = reference.reference_id
123
+
124
+ resolved_resource =
125
+ begin
126
+ if reference.relative?
127
+ begin
128
+ reference.resource_class
129
+ rescue NameError
130
+ return false
131
+ end
132
+
133
+ fhir_read(reference_type, reference_id)&.resource
134
+ elsif reference.base_uri.chomp('/') == fhir_client.instance_variable_get(:@base_service_url).chomp('/')
135
+ fhir_read(reference_type, reference_id)&.resource
136
+ else
137
+ get(reference.reference)&.resource
138
+ end
139
+ rescue StandardError => e
140
+ Inferno::Application['logger'].error("Unable to resolve reference #{reference.reference}")
141
+ Inferno::Application['logger'].error(e.full_message)
142
+ return false
143
+ end
144
+
145
+ return false unless resolved_resource&.resourceType == reference_type && resolved_resource&.id == reference_id
146
+
147
+ return false unless resource_is_valid_with_target_profile?(resolved_resource, target_profile)
148
+
149
+ record_resolved_reference(reference, target_profile)
150
+ true
151
+ end
152
+
153
+ def resource_is_valid_with_target_profile?(resource, target_profile)
154
+ return true if target_profile.blank?
155
+
156
+ # Only need to know if the resource is valid.
157
+ # Calling resource_is_valid? causes validation errors to be logged.
158
+ validator = find_validator(:default)
159
+
160
+ target_profile_with_version =
161
+ target_profile.include?('|') ? target_profile : "#{target_profile}|#{metadata.profile_version}"
162
+
163
+ outcome = FHIR::OperationOutcome.new(JSON.parse(validator.validate(resource, target_profile_with_version)))
164
+
165
+ message_hashes = outcome.issue&.map { |issue| validator.message_hash_from_issue(issue, resource) } || []
166
+
167
+ message_hashes.concat(validator.additional_validation_messages(resource, target_profile_with_version))
168
+
169
+ validator.filter_messages(message_hashes)
170
+
171
+ message_hashes.none? { |message_hash| message_hash[:type] == 'error' }
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,46 @@
1
+ module Inferno
2
+ module Utils
3
+ # @private
4
+ module Middleware
5
+ class RequestLogger
6
+ def log_response(response, start_time, end_time, exception = nil)
7
+ elapsed = end_time - start_time
8
+ status, _response_headers, body = response if response
9
+ status, = response if exception
10
+
11
+ logger.info("#{status} in #{elapsed.in_milliseconds} ms")
12
+ return unless body.present?
13
+
14
+ body = body.join if body.is_a?(Array)
15
+
16
+ if body.length > 100
17
+ logger.info("#{body[0..100]}...")
18
+ else
19
+ logger.info(body)
20
+ end
21
+ end
22
+
23
+ def log_request(env)
24
+ method = env['REQUEST_METHOD']
25
+ scheme = env['rack.url_scheme']
26
+ host = env['HTTP_HOST']
27
+ path = env['REQUEST_URI']
28
+ query = env['rack.request.query_string']
29
+ body = env['rack.input']
30
+ body = body.instance_of?(Puma::NullIO) ? nil : body.string
31
+ query_string = query.blank? ? '' : "?#{query}"
32
+
33
+ logger.info("#{method} #{scheme}://#{host}#{path}#{query_string}")
34
+
35
+ return unless body.present?
36
+
37
+ if body.length > 100
38
+ logger.info("#{body[0..100]}...")
39
+ else
40
+ logger.info(body)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end