davinci_pas_test_kit 0.12.0 → 0.12.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/davinci_pas_test_kit/client_suite.rb +24 -0
  3. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +29 -1
  4. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +27 -4
  5. data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_pended_pas_inquiry_request_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_inquire_request_bundle_validation_test.rb} +22 -20
  6. data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_denial_pas_response_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_inquire_response_bundle_validation_test.rb} +34 -21
  7. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +124 -5
  8. data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_pas_request_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_request_bundle_validation_test.rb} +22 -20
  9. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/{pas_client_approval_submit_response_attest.rb → pas_client_response_attest.rb} +26 -9
  10. data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_pended_pas_response_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_response_bundle_validation_test.rb} +44 -20
  11. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +49 -0
  12. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +48 -0
  13. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +21 -9
  14. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +2 -2
  15. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +21 -22
  16. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +97 -31
  17. data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +213 -72
  18. data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +85 -134
  19. data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +96 -0
  20. data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +90 -0
  21. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +0 -2
  22. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +3 -1
  23. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -1
  24. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +0 -2
  25. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +3 -1
  26. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +0 -4
  27. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -1
  28. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +1 -1
  29. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +6 -5
  30. data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +1 -0
  31. data/lib/davinci_pas_test_kit/generator/group_generator.rb +9 -8
  32. data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +7 -3
  33. data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +1 -0
  34. data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +19 -56
  35. data/lib/davinci_pas_test_kit/generator/value_extractor.rb +4 -1
  36. data/lib/davinci_pas_test_kit/generator.rb +1 -1
  37. data/lib/davinci_pas_test_kit/jobs/send_pas_subscription_notification.rb +136 -0
  38. data/lib/davinci_pas_test_kit/jobs/send_subscription_handshake.rb +139 -0
  39. data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +8 -7
  40. data/lib/davinci_pas_test_kit/response_generator.rb +397 -0
  41. data/lib/davinci_pas_test_kit/tags.rb +9 -0
  42. data/lib/davinci_pas_test_kit/urls.rb +8 -0
  43. data/lib/davinci_pas_test_kit/user_input_response.rb +11 -8
  44. data/lib/davinci_pas_test_kit/validation_test.rb +0 -1
  45. data/lib/davinci_pas_test_kit/version.rb +2 -2
  46. metadata +30 -14
  47. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +0 -38
  48. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +0 -39
  49. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +0 -35
  50. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +0 -39
  51. data/lib/davinci_pas_test_kit/generator/templates/validation_client.rb.erb +0 -50
@@ -0,0 +1,397 @@
1
+ module DaVinciPASTestKit
2
+ module ResponseGenerator
3
+ def mock_id_only_notification_bundle(submit_response, subscription_reference, subscription_topic)
4
+ notification_timestamp = Time.now.utc
5
+ mock_notification_bundle = build_mock_notification_bundle(notification_timestamp, subscription_reference,
6
+ subscription_topic, submit_response, 'id-only', nil)
7
+ mock_notification_bundle.to_json
8
+ end
9
+
10
+ def mock_full_resource_notification_bundle(submit_response, subscription_reference, subscription_topic, decision)
11
+ notification_timestamp = Time.now.utc
12
+ mock_notification_bundle = build_mock_notification_bundle(notification_timestamp, subscription_reference,
13
+ subscription_topic, submit_response, 'full-resource',
14
+ decision)
15
+ mock_notification_bundle.to_json
16
+ end
17
+
18
+ def mock_response_bundle(request_bundle, operation, decision, claim_response_uuid = nil)
19
+ mocked_timestamp = Time.now.utc
20
+ build_mock_response_bundle(request_bundle, operation, decision, mocked_timestamp, claim_response_uuid)&.to_json
21
+ end
22
+
23
+ # update things that tester cannot get right themselves
24
+ # - timestamps on the Bundle and ClaimResponse (can't predict the processing time)
25
+ # - reference to the submitted Claim (may not have control of created id). NOTE: this is likely
26
+ # incomplete - when the Claim is included, there are other things that
27
+ # need to be in the Bundle that may also not be controlled
28
+ def update_tester_provided_response(user_inputted_response, claim_full_url)
29
+ response_bundle = FHIR.from_contents(user_inputted_response)
30
+ return user_inputted_response unless response_bundle.present?
31
+
32
+ now = Time.now.utc
33
+ response_bundle.timestamp = now.iso8601 if response_bundle&.timestamp.present?
34
+ claim_response_entry = response_bundle&.entry&.find { |e| e&.resource&.resourceType == 'ClaimResponse' }
35
+ if claim_response_entry.present?
36
+ claim_response_entry.resource.created = now.iso8601 if claim_response_entry.resource.created
37
+ if claim_response_entry.resource.request.present? && claim_full_url.present?
38
+ claim_response_entry.resource.request.reference = claim_full_url
39
+ end
40
+ end
41
+ response_bundle.to_json
42
+ end
43
+
44
+ # update things that tester cannot get right themselves
45
+ # - reference to the ClaimResponse in the case that it is generated by Inferno.
46
+ # If the tester provided the ClaimResponse, they are responsible for aligning
47
+ # the notification with it.
48
+ # - notification timestamps
49
+ def update_tester_provided_notification(user_inputted_notification, generated_claim_response_uuid)
50
+ now = Time.now.utc
51
+
52
+ notification_bundle = FHIR.from_contents(user_inputted_notification)
53
+ subscription_status_entry = notification_bundle&.entry&.find { |e| e.resource&.resourceType == 'Parameters' }
54
+ subscription_status_resource = subscription_status_entry&.resource
55
+ event_parameter = subscription_status_resource&.parameter&.find { |p| p.name == 'notification-event' }
56
+
57
+ if generated_claim_response_uuid.present?
58
+ claim_response_full_url = "urn:uuid:#{generated_claim_response_uuid}"
59
+ focus_part = event_parameter&.part&.find { |pt| pt.name == 'focus' }
60
+ if focus_part.present?
61
+ existing_claim_response_reference = focus_part.valueReference&.reference
62
+ focus_part.valueReference.reference = claim_response_full_url
63
+ update_tester_provided_notification_claim_response_entry(notification_bundle,
64
+ generated_claim_response_uuid,
65
+ claim_response_full_url,
66
+ existing_claim_response_reference)
67
+
68
+ end
69
+ end
70
+
71
+ timestamp_part = event_parameter&.part&.find { |pt| pt.name == 'timestamp' }
72
+ timestamp_part.valueInstant = now.iso8601 if timestamp_part.present?
73
+ notification_bundle.timestamp = now.iso8601 if notification_bundle.timestamp.present?
74
+
75
+ notification_bundle.to_json
76
+ end
77
+
78
+ private
79
+
80
+ # if there's a ClaimResponse entry (full-resource or id-only without the resource contents)
81
+ # then update the fullUrl and id
82
+ def update_tester_provided_notification_claim_response_entry(notification_bundle, claim_response_id,
83
+ claim_response_full_url,
84
+ existing_claim_response_reference)
85
+ claim_response_entry = notification_bundle&.entry&.find { |e| e.resource&.resourceType == 'ClaimResponse' }
86
+ if claim_response_entry.blank? && existing_claim_response_reference.present?
87
+ claim_response_entry = notification_bundle&.entry&.find { |e| e.fullUrl == existing_claim_response_reference }
88
+ end
89
+ return unless claim_response_entry.present?
90
+
91
+ claim_response_entry.fullUrl = claim_response_full_url
92
+ return unless claim_response_entry.resource.present?
93
+
94
+ claim_response_entry.resource.id = claim_response_id
95
+ end
96
+
97
+ def build_mock_notification_bundle(notification_timestamp, subscription_reference, subscription_topic,
98
+ submit_response, type, decision)
99
+ submit_bundle = FHIR.from_contents(submit_response)
100
+ claim_response_full_url = claim_response_full_url_from_submit_response_bundle(submit_bundle)
101
+ mock_notification_bundle = FHIR::Bundle.new(
102
+ id: SecureRandom.uuid,
103
+ timestamp: notification_timestamp.iso8601,
104
+ type: 'history'
105
+ )
106
+
107
+ additional_context_references =
108
+ if type == 'full-resource' && submit_bundle.present? && submit_bundle.is_a?(FHIR::Bundle)
109
+ submit_bundle.entry.reject { |entry| entry.resource&.resourceType == 'ClaimResponse' }.map(&:fullUrl).compact
110
+ else
111
+ []
112
+ end
113
+ mock_notification_status = build_mock_notification_status(notification_timestamp, subscription_reference,
114
+ subscription_topic, claim_response_full_url,
115
+ additional_context_references)
116
+ mock_notification_bundle.entry << build_mock_notification_status_entry(mock_notification_status,
117
+ subscription_reference)
118
+ if type == 'full-resource' && submit_bundle.present? && submit_bundle.is_a?(FHIR::Bundle)
119
+ fhir_base_url = extract_fhir_base_url(subscription_reference)
120
+ submit_bundle.entry.each do |entry|
121
+ update_claim_response_decisions(entry.resource, decision) if entry.resource.resourceType == 'ClaimResponse'
122
+ entry.request = FHIR::Bundle::Entry::Request.new(
123
+ method: 'POST',
124
+ url: "#{fhir_base_url}/#{entry.resource.resourceType}"
125
+ )
126
+ entry.response = FHIR::Bundle::Entry::Response.new(
127
+ status: '201'
128
+ )
129
+ mock_notification_bundle.entry << entry
130
+ end
131
+ end
132
+
133
+ mock_notification_bundle
134
+ end
135
+
136
+ def update_claim_response_decisions(claim_response, decision)
137
+ claim_response&.item&.each do |item|
138
+ item.adjudication.each do |adjudication|
139
+ review_action_extension = adjudication.extension.find do |ext|
140
+ ext.url == 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewAction'
141
+ end
142
+ review_code_extension = review_action_extension&.extension&.find do |ext|
143
+ ext.url == 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewActionCode'
144
+ end
145
+ next unless review_code_extension.present?
146
+
147
+ review_code_extension.valueCodeableConcept = FHIR::CodeableConcept.new(
148
+ coding: [
149
+ get_review_action_code(decision)
150
+ ]
151
+ )
152
+ end
153
+ end
154
+ end
155
+
156
+ def build_mock_response_bundle(request_bundle, operation, decision, timestamp, claim_response_uuid = nil)
157
+ claim_entry = request_bundle&.entry&.find { |e| e&.resource&.resourceType == 'Claim' }
158
+ claim_full_url = claim_entry&.fullUrl
159
+ return nil if claim_entry.blank? || claim_full_url.blank?
160
+
161
+ root_url = extract_fhir_base_url(claim_full_url)
162
+ mocked_claim_response = build_mock_claim_response(claim_entry.resource, request_bundle, root_url, operation,
163
+ decision, timestamp, claim_response_uuid)
164
+ build_mock_bundle(mocked_claim_response, request_bundle, root_url, operation, timestamp)
165
+ end
166
+
167
+ # Note that references from the claim to other resources in the bundle need to be changed to absolute URLs
168
+ # if they are relative, because the ClaimResponse's fullUrl is a urn:uuid
169
+ #
170
+ # @private
171
+ def build_mock_claim_response(claim, request_bundle, root_url, operation, decision, timestamp,
172
+ claim_response_uuid = nil)
173
+ claim_response_uuid = SecureRandom.uuid if claim_response_uuid.blank?
174
+ return FHIR::ClaimResponse.new(id: claim_response_uuid) if claim.blank?
175
+
176
+ FHIR::ClaimResponse.new(
177
+ id: claim_response_uuid,
178
+ meta: FHIR::Meta.new(profile: if operation == 'submit'
179
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claimresponse'
180
+ else
181
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claiminquiryresponse'
182
+ end),
183
+ identifier: claim.identifier,
184
+ type: claim.type,
185
+ status: claim.status,
186
+ use: claim.use,
187
+ patient: absolute_reference(claim.patient, request_bundle.entry, root_url),
188
+ created: timestamp.iso8601,
189
+ insurer: absolute_reference(claim.insurer, request_bundle.entry, root_url),
190
+ requestor: absolute_reference(claim.provider, request_bundle.entry, root_url),
191
+ outcome: 'complete',
192
+ item: claim.item.map do |item|
193
+ FHIR::ClaimResponse::Item.new(
194
+ extension: [
195
+ FHIR::Extension.new(
196
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthIssueDate',
197
+ valueDate: timestamp.strftime('%Y-%m-%d')
198
+ ),
199
+ FHIR::Extension.new(
200
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthPeriod',
201
+ valuePeriod: FHIR::Period.new(start: timestamp.strftime('%Y-%m-%d'),
202
+ end: (timestamp + 1.month).strftime('%Y-%m-%d'))
203
+ )
204
+ ],
205
+ itemSequence: item.sequence,
206
+ adjudication: [
207
+ FHIR::ClaimResponse::Item::Adjudication.new(
208
+ extension: [
209
+ FHIR::Extension.new(
210
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewAction',
211
+ extension: [
212
+ FHIR::Extension.new(
213
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewActionCode',
214
+ valueCodeableConcept: FHIR::CodeableConcept.new(
215
+ coding: [
216
+ get_review_action_code(decision)
217
+ ]
218
+ )
219
+ )
220
+ ]
221
+ )
222
+ ],
223
+ category: FHIR::CodeableConcept.new(
224
+ coding: [
225
+ FHIR::Coding.new(system: 'http://terminology.hl7.org/CodeSystem/adjudication', code: 'submitted')
226
+ ]
227
+ )
228
+ )
229
+ ]
230
+ )
231
+ end
232
+ )
233
+ end
234
+
235
+ def build_mock_bundle(claim_response, request_bundle, root_url, operation, timestamp)
236
+ response_bundle = FHIR::Bundle.new(
237
+ id: SecureRandom.uuid,
238
+ meta: FHIR::Meta.new(profile: if operation == 'submit'
239
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-pas-response-bundle'
240
+ else
241
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-pas-inquiry-response-bundle'
242
+ end),
243
+ timestamp: timestamp.iso8601,
244
+ type: 'collection',
245
+ entry: [
246
+ FHIR::Bundle::Entry.new(fullUrl: "urn:uuid:#{claim_response.id}",
247
+ resource: claim_response)
248
+ ]
249
+ )
250
+ response_bundle.entry.concat(referenced_entities(claim_response, request_bundle.entry, root_url))
251
+ response_bundle
252
+ end
253
+
254
+ def build_mock_notification_status_entry(status_parameters, subscription_reference)
255
+ FHIR::Bundle::Entry.new(fullUrl: "urn:uuid:#{SecureRandom.uuid}",
256
+ resource: status_parameters,
257
+ request: FHIR::Bundle::Entry::Request.new(
258
+ method: 'GET',
259
+ url: "#{subscription_reference}/$status"
260
+ ),
261
+ response: FHIR::Bundle::Entry::Response.new(
262
+ status: '200'
263
+ ))
264
+ end
265
+
266
+ def build_mock_notification_status(timestamp, subscription_reference, subscription_topic, claim_response_reference,
267
+ additional_context_references)
268
+ status_parameters = FHIR::Parameters.new
269
+ status_parameters.parameter << FHIR::Parameters::Parameter.new(
270
+ name: 'subscription',
271
+ valueReference: FHIR::Reference.new(
272
+ reference: subscription_reference
273
+ )
274
+ )
275
+ status_parameters.parameter << FHIR::Parameters::Parameter.new(
276
+ name: 'topic',
277
+ valueCanonical: subscription_topic
278
+ )
279
+ status_parameters.parameter << FHIR::Parameters::Parameter.new(
280
+ name: 'status',
281
+ valueCode: 'active'
282
+ )
283
+ status_parameters.parameter << FHIR::Parameters::Parameter.new(
284
+ name: 'type',
285
+ valueCode: 'event-notification'
286
+ )
287
+ status_parameters.parameter << FHIR::Parameters::Parameter.new(
288
+ name: 'events-since-subscription-start',
289
+ valueString: '1'
290
+ )
291
+ if claim_response_reference.present?
292
+ event = FHIR::Parameters::Parameter.new(
293
+ name: 'notification-event'
294
+ )
295
+ status_parameters.parameter << event
296
+ event.part << FHIR::Parameters::Parameter.new(
297
+ name: 'event-number',
298
+ valueString: '1'
299
+ )
300
+ event.part << FHIR::Parameters::Parameter.new(
301
+ name: 'timestamp',
302
+ valueInstant: timestamp.iso8601
303
+ )
304
+ event.part << FHIR::Parameters::Parameter.new(
305
+ name: 'focus',
306
+ valueReference: FHIR::Reference.new(
307
+ reference: claim_response_reference
308
+ )
309
+ )
310
+ additional_context_references&.each do |reference|
311
+ event.part << FHIR::Parameters::Parameter.new(
312
+ name: 'additional-context',
313
+ valueReference: FHIR::Reference.new(
314
+ reference:
315
+ )
316
+ )
317
+ end
318
+ end
319
+ status_parameters
320
+ end
321
+
322
+ def get_review_action_code(decision)
323
+ case decision
324
+ when :denial
325
+ code = 'A3'
326
+ display = 'Not Certified'
327
+ when :pended
328
+ code = 'A4'
329
+ display = 'Pending'
330
+ else # approval or no workflow
331
+ code = 'A1'
332
+ display = 'Certified in total'
333
+ end
334
+ FHIR::Coding.new(
335
+ system: 'https://codesystem.x12.org/005010/306',
336
+ code:,
337
+ display:
338
+ )
339
+ end
340
+
341
+ def absolute_reference(ref, entries, root_url)
342
+ url = find_matching_entry(ref&.reference, entries, root_url)&.fullUrl
343
+ ref.reference = url if url
344
+ ref
345
+ end
346
+
347
+ def referenced_entities(resource, entries, root_url)
348
+ matches = []
349
+ attributes = resource&.source_hash&.keys
350
+ attributes.each do |attr|
351
+ value = resource.send(attr.to_sym)
352
+ if value.is_a?(FHIR::Reference) && value.reference.present?
353
+ match = find_matching_entry(value.reference, entries, root_url)
354
+ if match.present? && matches.none?(match)
355
+ value.reference = match.fullUrl
356
+ matches.concat([match], referenced_entities(match.resource, entries, root_url))
357
+ end
358
+ elsif value.is_a?(Array) && value.all? { |elmt| elmt.is_a?(FHIR::Model) }
359
+ value.each { |val| matches.concat(referenced_entities(val, entries, root_url)) }
360
+ end
361
+ end
362
+
363
+ matches
364
+ end
365
+
366
+ def find_matching_entry(ref, entries, root_url = '')
367
+ ref = "#{root_url}/#{ref}" if relative_reference?(ref) && root_url&.present?
368
+
369
+ entries&.find { |entry| entry&.fullUrl == ref }
370
+ end
371
+
372
+ def relative_reference?(ref)
373
+ ref&.count('/') == 1
374
+ end
375
+
376
+ # Drop the last two segments of a URL, i.e. the resource type and ID of a FHIR resource
377
+ # e.g. http://example.org/fhir/Patient/123 -> http://example.org/fhir
378
+ def extract_fhir_base_url(url)
379
+ return unless url&.start_with?('http://', 'https://')
380
+
381
+ # Drop everything after the second to last '/', ignoring a trailing slash
382
+ url.sub(%r{/[^/]*/[^/]*(/)?\z}, '')
383
+ end
384
+
385
+ def claim_response_full_url_from_submit_response_bundle(submit_bundle)
386
+ claim_response_entry = submit_bundle&.entry&.find { |e| e&.resource&.resourceType == 'ClaimResponse' }
387
+
388
+ if claim_response_entry.present?
389
+ return claim_response_entry.fullUrl unless claim_response_entry.fullUrl.blank?
390
+ unless claim_response_entry.resource.blank? || claim_response_entry.resource.id.blank?
391
+ return claim_response_entry.resource.id
392
+ end
393
+ end
394
+ "urn:uuid:#{SecureRandom.uuid}"
395
+ end
396
+ end
397
+ end
@@ -4,4 +4,13 @@ module DaVinciPASTestKit
4
4
  AUTH_TAG = 'pas_auth'
5
5
  SUBMIT_TAG = 'pas_submit'
6
6
  INQUIRE_TAG = 'pas_inquire'
7
+ APPROVAL_WORKFLOW_TAG = 'pas_approved_workflow'
8
+ DENIAL_WORKFLOW_TAG = 'pas_denied_workflow'
9
+ PENDED_WORKFLOW_TAG = 'pas_pended_workflow'
10
+ NOTIFICATION_TAG = 'subscription_notification'
11
+ SUBSCRIPTION_CREATE_TAG = 'subscription_create'
12
+ SUBSCRIPTION_READ_TAG = 'subscription_read'
13
+ SUBSCRIPTION_STATUS_TAG = 'subscription_status'
14
+ REST_HOOK_HANDSHAKE_NOTIFICATION_TAG = 'rest_hook_handshake_notification'
15
+ REST_HOOK_EVENT_NOTIFICATION_TAG = 'rest_hook_event_notification'
7
16
  end
@@ -4,6 +4,10 @@ module DaVinciPASTestKit
4
4
  TOKEN_PATH = '/mock_auth/token'
5
5
  SUBMIT_PATH = '/fhir/Claim/$submit'
6
6
  INQUIRE_PATH = '/fhir/Claim/$inquire'
7
+ FHIR_SUBSCRIPTION_PATH = '/fhir/Subscription'
8
+ FHIR_SUBSCRIPTION_INSTANCE_PATH = '/fhir/Subscription/:id'
9
+ FHIR_SUBSCRIPTION_INSTANCE_STATUS_PATH = '/fhir/Subscription/:id/$status'
10
+ FHIR_SUBSCRIPTION_RESOURCE_STATUS_PATH = '/fhir/Subscription/$status'
7
11
  RESUME_PASS_PATH = '/resume_pass'
8
12
  RESUME_FAIL_PATH = '/resume_fail'
9
13
 
@@ -24,6 +28,10 @@ module DaVinciPASTestKit
24
28
  @inquire_url ||= base_url + INQUIRE_PATH
25
29
  end
26
30
 
31
+ def fhir_subscription_url
32
+ @fhir_subscription_url ||= base_url + FHIR_SUBSCRIPTION_PATH
33
+ end
34
+
27
35
  def resume_pass_url
28
36
  @resume_pass_url ||= base_url + RESUME_PASS_PATH
29
37
  end
@@ -4,8 +4,9 @@ module DaVinciPASTestKit
4
4
  klass.extend ClassMethods
5
5
  end
6
6
 
7
- def self.user_inputted_response(configurable, result)
8
- input_key = configurable.config.options[:respond_with]
7
+ def self.user_inputted_response(configurable, operation, result)
8
+ config_key = operation == 'submit' ? :submit_respond_with : :inquire_respond_with
9
+ input_key = configurable.config.options[config_key]
9
10
  return unless input_key.present?
10
11
 
11
12
  JSON.parse(result.input_json)&.find { |i| i['name'] == input_key.to_s }&.dig('value')
@@ -13,10 +14,8 @@ module DaVinciPASTestKit
13
14
  nil
14
15
  end
15
16
 
16
- def check_user_inputted_response(input_key, message = nil)
17
- skip_if send(input_key).blank?,
18
- message ||
19
- "To run this test a response body must be provided in the '**#{input_title(input_key)}**' input"
17
+ def user_inputted_response?(input_key)
18
+ send(input_key).present?
20
19
  end
21
20
 
22
21
  def input_title(input_key)
@@ -24,8 +23,12 @@ module DaVinciPASTestKit
24
23
  end
25
24
 
26
25
  module ClassMethods
27
- def respond_with(key)
28
- config options: { respond_with: key }
26
+ def submit_respond_with(key)
27
+ config options: { submit_respond_with: key }
28
+ end
29
+
30
+ def inquire_respond_with(key)
31
+ config options: { inquire_respond_with: key }
29
32
  end
30
33
  end
31
34
  end
@@ -9,7 +9,6 @@ module DaVinciPASTestKit
9
9
  profile_url,
10
10
  profile_version,
11
11
  skip_if_empty: true)
12
-
13
12
  skip_if skip_if_empty && resources.blank?,
14
13
  "No #{resource_type} resources conforming to the #{profile_url} profile were returned"
15
14
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DaVinciPASTestKit
4
- VERSION = '0.12.0'
5
- LAST_UPDATED = '2025-02-25'
4
+ VERSION = '0.12.1'
5
+ LAST_UPDATED = '2025-03-07'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: davinci_pas_test_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inferno Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-26 00:00:00.000000000 Z
11
+ date: 2025-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inferno_core
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.6.2
19
+ version: 0.6.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.6.2
26
+ version: 0.6.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: subscriptions_test_kit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.10.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: database_cleaner-sequel
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -93,16 +107,18 @@ files:
93
107
  - lib/davinci_pas_test_kit.rb
94
108
  - lib/davinci_pas_test_kit/client_suite.rb
95
109
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/claim_response_decision/pas_claim_response_decision_test.rb
96
- - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_response_attest.rb
97
110
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb
98
- - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb
99
111
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb
100
112
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_must_support_test.rb
101
- - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb
102
- - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb
103
- - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb
113
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_request_bundle_validation_test.rb
114
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_response_bundle_validation_test.rb
104
115
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb
116
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_request_bundle_validation_test.rb
117
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_response_attest.rb
118
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_response_bundle_validation_test.rb
105
119
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_submit_must_support_test.rb
120
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb
121
+ - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb
106
122
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_request_test.rb
107
123
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_validation_test.rb
108
124
  - lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/nonconformant_pas_bundle.json
@@ -124,6 +140,8 @@ files:
124
140
  - lib/davinci_pas_test_kit/docs/client_suite_description_v201.md
125
141
  - lib/davinci_pas_test_kit/docs/server_suite_description_v201.md
126
142
  - lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb
143
+ - lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb
144
+ - lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb
127
145
  - lib/davinci_pas_test_kit/endpoints/token_endpoint.rb
128
146
  - lib/davinci_pas_test_kit/fhir_resource_navigation.rb
129
147
  - lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/client_inquire_request_beneficiary_must_support_test.rb
@@ -146,10 +164,6 @@ files:
146
164
  - lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/server_inquire_response_claiminquiryresponse_must_support_test.rb
147
165
  - lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/metadata.yml
148
166
  - lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/server_submit_response_claimresponse_must_support_test.rb
149
- - lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_denial_pas_response_bundle_validation_test.rb
150
- - lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pas_request_bundle_validation_test.rb
151
- - lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pended_pas_inquiry_request_bundle_validation_test.rb
152
- - lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pended_pas_response_bundle_validation_test.rb
153
167
  - lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/metadata.yml
154
168
  - lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/server_submit_response_communication_request_must_support_test.rb
155
169
  - lib/davinci_pas_test_kit/generated/v2.0.1/coverage/client_inquire_request_coverage_must_support_test.rb
@@ -252,15 +266,17 @@ files:
252
266
  - lib/davinci_pas_test_kit/generator/templates/resource_list.rb.erb
253
267
  - lib/davinci_pas_test_kit/generator/templates/suite.rb.erb
254
268
  - lib/davinci_pas_test_kit/generator/templates/validation.rb.erb
255
- - lib/davinci_pas_test_kit/generator/templates/validation_client.rb.erb
256
269
  - lib/davinci_pas_test_kit/generator/terminology_binding_metadata_extractor.rb
257
270
  - lib/davinci_pas_test_kit/generator/validation_test_generator.rb
258
271
  - lib/davinci_pas_test_kit/generator/value_extractor.rb
259
272
  - lib/davinci_pas_test_kit/igs/README.md
260
273
  - lib/davinci_pas_test_kit/igs/davinci_pas_2.0.1.tgz
274
+ - lib/davinci_pas_test_kit/jobs/send_pas_subscription_notification.rb
275
+ - lib/davinci_pas_test_kit/jobs/send_subscription_handshake.rb
261
276
  - lib/davinci_pas_test_kit/metadata.rb
262
277
  - lib/davinci_pas_test_kit/must_support_test.rb
263
278
  - lib/davinci_pas_test_kit/pas_bundle_validation.rb
279
+ - lib/davinci_pas_test_kit/response_generator.rb
264
280
  - lib/davinci_pas_test_kit/tags.rb
265
281
  - lib/davinci_pas_test_kit/urls.rb
266
282
  - lib/davinci_pas_test_kit/user_input_response.rb
@@ -1,38 +0,0 @@
1
- require_relative '../../../urls'
2
-
3
- module DaVinciPASTestKit
4
- module DaVinciPASV201
5
- class PASClientDenialSubmitResponseAttest < Inferno::Test
6
- include URLs
7
-
8
- id :pas_client_v201_denial_submit_response_attest
9
- title 'Check that the client registers the request as denied (Attestation)'
10
- description %(
11
- This test provides the tester an opportunity to observe their client following
12
- the receipt of the denied response and attest that users are able to determine
13
- that the response has been denied.
14
- )
15
- input :access_token,
16
- title: 'Access Token',
17
- description: %(
18
- Access token that the client will provide in the Authorization header of each request
19
- made during this test.
20
- )
21
-
22
- run do
23
- wait(
24
- identifier: access_token,
25
- message: %(
26
- **Denial Workflow Test**:
27
-
28
- I attest that the client system displays the submitted claim as 'denied'.
29
-
30
- [Click here](#{resume_pass_url}?token=#{access_token}) if the above statement is true.
31
-
32
- [Click here](#{resume_fail_url}?token=#{access_token}) if the above statement is false.
33
- )
34
- )
35
- end
36
- end
37
- end
38
- end