davinci_pas_test_kit 0.12.2 → 0.13.0

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/pas_server_subscription_creation_against_pas_client.json +32 -0
  3. data/lib/davinci_pas_test_kit/certs/InfernoCA.key +52 -0
  4. data/lib/davinci_pas_test_kit/certs/InfernoCA.pem +35 -0
  5. data/lib/davinci_pas_test_kit/certs/TestKit.pem +32 -0
  6. data/lib/davinci_pas_test_kit/certs/TestKitPrivateKey.key +28 -0
  7. data/lib/davinci_pas_test_kit/client_suite.rb +141 -100
  8. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/configuration_other_display_test.rb +46 -0
  9. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/configuration_smart_display_test.rb +37 -0
  10. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/configuration_udap_display_test.rb +37 -0
  11. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/other_auth_attest_test.rb +36 -0
  12. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +21 -10
  13. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +20 -10
  14. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_must_support_test.rb +21 -9
  15. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +24 -14
  16. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_response_attest.rb +4 -9
  17. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_submit_must_support_test.rb +21 -9
  18. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +23 -13
  19. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +5 -24
  20. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_inquiry_error_test.rb +1 -0
  21. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_submission_error_test.rb +3 -1
  22. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/notification/pas_subscription_notification_test.rb +24 -20
  23. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +6 -4
  24. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_auth_smart_group.rb +32 -0
  25. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_auth_udap_group.rb +31 -0
  26. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +10 -4
  27. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_must_support_group.rb +29 -0
  28. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_options.rb +25 -0
  29. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +11 -4
  30. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_registration_group.rb +63 -0
  31. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_subscription_setup_group.rb +23 -0
  32. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_workflows_group.rb +21 -0
  33. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_server_subscription_input_conformance.rb +35 -0
  34. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_server_subscription_setup.rb +43 -0
  35. data/lib/davinci_pas_test_kit/descriptions.rb +10 -0
  36. data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +203 -79
  37. data/lib/davinci_pas_test_kit/docs/demo/PAS Client Suite Demonstration.postman_collection.json +246 -0
  38. data/lib/davinci_pas_test_kit/docs/server_suite_description_v201.md +21 -10
  39. data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +13 -1
  40. data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +13 -1
  41. data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +10 -1
  42. data/lib/davinci_pas_test_kit/endpoints/token_endpoint.rb +29 -15
  43. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/metadata.yml +2 -1
  44. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/claim_operation_test.rb +1 -2
  45. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/metadata.yml +28 -17
  46. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/claim_inquiry_operation_test.rb +1 -2
  47. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/client_inquire_request_claim_inquiry_must_support_test.rb +4 -1
  48. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/metadata.yml +7 -1
  49. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/server_inquire_request_claim_inquiry_must_support_test.rb +3 -1
  50. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/client_submit_request_claim_update_must_support_test.rb +9 -3
  51. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/metadata.yml +28 -17
  52. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/server_submit_request_claim_update_must_support_test.rb +9 -3
  53. data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/metadata.yml +6 -0
  54. data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/server_inquire_response_claiminquiryresponse_must_support_test.rb +1 -1
  55. data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/metadata.yml +8 -0
  56. data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/server_submit_response_claimresponse_must_support_test.rb +1 -1
  57. data/lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/metadata.yml +4 -0
  58. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/metadata.yml +11 -5
  59. data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/metadata.yml +2 -0
  60. data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/metadata.yml +6 -0
  61. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/metadata.yml +4 -2
  62. data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/metadata.yml +2 -0
  63. data/lib/davinci_pas_test_kit/generated/v2.0.1/metadata.yml +153 -53
  64. data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/metadata.yml +2 -0
  65. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +2 -1
  66. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +0 -2
  67. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/metadata.yml +2 -1
  68. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -3
  69. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +2 -1
  70. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +0 -2
  71. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +2 -1
  72. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -3
  73. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_approval_use_case_group.rb +0 -1
  74. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +0 -1
  75. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_must_support_use_case_group.rb +3 -0
  76. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +10 -3
  77. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/metadata.yml +2 -1
  78. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/metadata.yml +4 -0
  79. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/metadata.yml +4 -2
  80. data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +8 -5
  81. data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/metadata.yml +2 -0
  82. data/lib/davinci_pas_test_kit/generated/v2.0.1/task/metadata.yml +4 -0
  83. data/lib/davinci_pas_test_kit/generator/group_generator.rb +20 -4
  84. data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +2 -2
  85. data/lib/davinci_pas_test_kit/generator/ig_resources.rb +4 -0
  86. data/lib/davinci_pas_test_kit/generator/must_support_test_generator.rb +14 -3
  87. data/lib/davinci_pas_test_kit/generator/operation_test_generator.rb +16 -3
  88. data/lib/davinci_pas_test_kit/generator/templates/group.rb.erb +10 -3
  89. data/lib/davinci_pas_test_kit/generator/templates/must_support.rb.erb +3 -0
  90. data/lib/davinci_pas_test_kit/generator/templates/operation.rb.erb +4 -1
  91. data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +8 -5
  92. data/lib/davinci_pas_test_kit/generator/templates/validation.rb.erb +5 -4
  93. data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +12 -1
  94. data/lib/davinci_pas_test_kit/must_support_test.rb +2 -202
  95. data/lib/davinci_pas_test_kit/pas_subscription_verification.rb +30 -0
  96. data/lib/davinci_pas_test_kit/requirements/generated/davinci-pas-test-kit_requirements_coverage.csv +58 -58
  97. data/lib/davinci_pas_test_kit/session_identification.rb +45 -0
  98. data/lib/davinci_pas_test_kit/tags.rb +1 -0
  99. data/lib/davinci_pas_test_kit/urls.rb +61 -9
  100. data/lib/davinci_pas_test_kit/version.rb +2 -2
  101. data/lib/requirements_config.yaml +1 -1
  102. metadata +54 -8
  103. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_request_test.rb +0 -31
  104. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_validation_test.rb +0 -18
  105. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +0 -49
  106. data/lib/davinci_pas_test_kit/generator/must_support_metadata_extractor.rb +0 -327
@@ -52,13 +52,9 @@ module DaVinciPASTestKit
52
52
  def perform_must_support_test(resources)
53
53
  assert resources.present?, "No #{resource_type} resources were found"
54
54
 
55
- missing_elements(resources)
56
- missing_slices(resources)
57
- missing_extensions(resources)
55
+ missing_must_support_strings = missing_must_support_elements(resources, nil, metadata:)
58
56
 
59
- handle_must_support_choices if metadata.must_supports[:choices].present?
60
-
61
- return unless (missing_elements + missing_slices + missing_extensions).compact.reject(&:empty?).present?
57
+ return unless missing_must_support_strings.any?
62
58
 
63
59
  all_must_support_errors << "Could not find #{missing_must_support_strings.join(', ')} " \
64
60
  "in the #{resources.length} provided #{resource_type} resource(s)."
@@ -71,201 +67,5 @@ module DaVinciPASTestKit
71
67
  # assert false, "Could not find #{missing_must_support_strings.join(', ')} in the #{resources.length} " \
72
68
  # "provided #{resource_type} resource(s)"
73
69
  end
74
-
75
- def handle_must_support_choices
76
- missing_elements.delete_if do |element|
77
- choices = metadata.must_supports[:choices].find { |choice| choice[:paths]&.include?(element[:path]) }
78
- is_any_choice_supported?(choices)
79
- end
80
-
81
- missing_extensions.delete_if do |extension|
82
- choices = metadata.must_supports[:choices].find { |choice| choice[:extension_ids]&.include?(extension[:id]) }
83
- is_any_choice_supported?(choices)
84
- end
85
-
86
- missing_slices.delete_if do |slice|
87
- choices = metadata.must_supports[:choices].find { |choice| choice[:slice_names]&.include?(slice[:slice_id]) }
88
- is_any_choice_supported?(choices)
89
- end
90
- end
91
-
92
- def is_any_choice_supported?(choices)
93
- choices.present? &&
94
- (
95
- choices[:paths]&.any? { |path| missing_elements.none? { |element| element[:path] == path } } ||
96
- choices[:extension_ids]&.any? do |extension_id|
97
- missing_extensions.none? do |extension|
98
- extension[:id] == extension_id
99
- end
100
- end ||
101
- choices[:slice_names]&.any? { |slice_name| missing_slices.none? { |slice| slice[:slice_id] == slice_name } }
102
- )
103
- end
104
-
105
- def missing_must_support_strings
106
- missing_elements.map { |element_definition| missing_element_string(element_definition) } +
107
- missing_slices.map { |slice_definition| slice_definition[:name] } +
108
- missing_extensions.map { |extension_definition| extension_definition[:id] }
109
- end
110
-
111
- def missing_element_string(element_definition)
112
- if element_definition[:fixed_value].present?
113
- "#{element_definition[:path]}:#{element_definition[:fixed_value]}"
114
- else
115
- element_definition[:path]
116
- end
117
- end
118
-
119
- def exclude_uscdi_only_test?
120
- config.options[:exclude_uscdi_only_test] == true
121
- end
122
-
123
- def must_support_extensions
124
- if exclude_uscdi_only_test?
125
- metadata.must_supports[:extensions].reject { |extension| extension[:uscdi_only] }
126
- else
127
- metadata.must_supports[:extensions]
128
- end
129
- end
130
-
131
- def missing_extensions(resources = [])
132
- @missing_extensions ||=
133
- must_support_extensions.select do |extension_definition|
134
- resources.none? do |resource|
135
- path = extension_definition[:path]
136
- if extension_definition[:path] == 'extension'
137
- resource.extension.any? { |extension| extension.url == extension_definition[:url] }
138
- else
139
- extension = find_a_value_at(resource, path) do |el|
140
- el.url == extension_definition[:url]
141
- end
142
-
143
- extension&.url == extension_definition[:url]
144
- end
145
- end
146
- end
147
- end
148
-
149
- def must_support_elements
150
- if exclude_uscdi_only_test?
151
- metadata.must_supports[:elements].reject { |element| element[:uscdi_only] }
152
- else
153
- metadata.must_supports[:elements]
154
- end
155
- end
156
-
157
- def missing_elements(resources = [])
158
- @missing_elements ||=
159
- must_support_elements.select do |element_definition|
160
- # PAS: The MS Claim.supportingInfo slices do not have timing[x]
161
- next if resource_type == 'Claim' && element_definition[:path] == 'supportingInfo.timing[x]'
162
-
163
- resources.none? do |resource|
164
- path = element_definition[:path] # .delete_suffix('[x]')
165
- value_found = find_a_value_at(resource, path) do |value|
166
- value_without_extensions =
167
- value.respond_to?(:to_hash) ? value.to_hash.except('extension') : value
168
-
169
- (value_without_extensions.present? || value_without_extensions == false) &&
170
- (element_definition[:fixed_value].blank? || value == element_definition[:fixed_value])
171
- end
172
- # Note that false.present? => false, which is why we need to add this extra check
173
- value_found.present? || value_found == false
174
- end
175
- end
176
- @missing_elements.compact
177
- end
178
-
179
- def must_support_slices
180
- if exclude_uscdi_only_test?
181
- metadata.must_supports[:slices].reject { |slice| slice[:uscdi_only] }
182
- else
183
- metadata.must_supports[:slices]
184
- end
185
- end
186
-
187
- def missing_slices(resources = [])
188
- @missing_slices ||=
189
- must_support_slices.select do |slice|
190
- resources.none? do |resource|
191
- path = slice[:path] # .delete_suffix('[x]')
192
- find_slice(resource, path, slice[:discriminator]).present?
193
- end
194
- end
195
- end
196
-
197
- def find_slice(resource, path, discriminator)
198
- find_a_value_at(resource, path) do |element|
199
- case discriminator[:type]
200
- when 'patternCodeableConcept'
201
- coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
202
- find_a_value_at(element, coding_path) do |coding|
203
- coding.code == discriminator[:code] && coding.system == discriminator[:system]
204
- end
205
- when 'patternCoding'
206
- coding_path = discriminator[:path].present? ? discriminator[:path] : ''
207
- find_a_value_at(element, coding_path) do |coding|
208
- coding.code == discriminator[:code] && coding.system == discriminator[:system]
209
- end
210
- when 'patternIdentifier'
211
- find_a_value_at(element, discriminator[:path]) { |identifier| identifier.system == discriminator[:system] }
212
- when 'value'
213
- values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
214
- find_slice_by_values(element, values)
215
- when 'type'
216
- case discriminator[:code]
217
- when 'Date'
218
- begin
219
- Date.parse(element)
220
- rescue ArgumentError
221
- false
222
- end
223
- when 'DateTime'
224
- begin
225
- DateTime.parse(element)
226
- rescue ArgumentError
227
- false
228
- end
229
- when 'String'
230
- element.is_a? String
231
- else
232
- res = element.try(:resource) || element
233
- res.is_a? FHIR.const_get(discriminator[:code])
234
- end
235
- when 'requiredBinding'
236
- coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
237
- find_a_value_at(element, coding_path) { |coding| discriminator[:values].include?(coding.code) }
238
- end
239
- end
240
- end
241
-
242
- def find_slice_by_values(element, value_definitions)
243
- path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
244
- Array.wrap(element).find do |el|
245
- path_prefixes.all? do |path_prefix|
246
- value_definitions_for_path =
247
- value_definitions
248
- .select { |value_definition| value_definition[:path].first == path_prefix }
249
- .each { |value_definition| value_definition[:path].shift }
250
- find_a_value_at(el, path_prefix) do |el_found|
251
- child_element_value_definitions, current_element_value_definitions =
252
- value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }
253
- current_element_values_match = current_element_value_definitions.all? do |value_definition|
254
- (value_definition[:value].present? && value_definition[:value] == el_found) ||
255
- (value_definition[:value].blank? && el_found.present?)
256
- end
257
-
258
- child_element_values_match =
259
- if child_element_value_definitions.present?
260
- find_slice_by_values(el_found, child_element_value_definitions)
261
- else
262
- true
263
- end
264
-
265
- current_element_values_match && child_element_values_match
266
- end
267
- end
268
- end
269
- end
270
70
  end
271
71
  end
@@ -0,0 +1,30 @@
1
+ module DaVinciPASTestKit
2
+ module PASSubscriptionVerification
3
+ def verify_pas_subscription(subscription_json_str)
4
+ assert_valid_json(subscription_json_str)
5
+ subscription = JSON.parse(subscription_json_str)
6
+
7
+ unless subscription['criteria'] == 'http://hl7.org/fhir/us/davinci-pas/SubscriptionTopic/PASSubscriptionTopic'
8
+ add_message('error', %(
9
+ The Subscription must use the PAS-defined Subscription topic
10
+ `http://hl7.org/fhir/us/davinci-pas/SubscriptionTopic/PASSubscriptionTopic`
11
+ in the `Subscription.criteria` element.
12
+ ))
13
+ end
14
+
15
+ filter_criteria = subscription.dig('_criteria', 'extension')
16
+ &.select do |ext|
17
+ ext['url'] == 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-filter-criteria'
18
+ end
19
+ unless filter_criteria&.length == 1
20
+ add_message('error', %(
21
+ The Subscription must include a single filter on the submitting organization
22
+ in the `Subscription.criteria.extension` element.
23
+ ))
24
+ end
25
+
26
+ assert messages.none? { |msg| msg[:type] == 'error' },
27
+ 'The Subscription does not conform to PAS requirements - see messages for details.'
28
+ end
29
+ end
30
+ end