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.
- checksums.yaml +4 -4
- data/lib/davinci_pas_test_kit/client_suite.rb +24 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +29 -1
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +27 -4
- 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
- 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
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +124 -5
- 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
- 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
- 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
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +49 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +48 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +21 -9
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +2 -2
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +21 -22
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +97 -31
- data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +213 -72
- data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +85 -134
- data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +96 -0
- data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +90 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +0 -2
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +3 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +0 -2
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +3 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +0 -4
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +1 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +6 -5
- data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +1 -0
- data/lib/davinci_pas_test_kit/generator/group_generator.rb +9 -8
- data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +7 -3
- data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +1 -0
- data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +19 -56
- data/lib/davinci_pas_test_kit/generator/value_extractor.rb +4 -1
- data/lib/davinci_pas_test_kit/generator.rb +1 -1
- data/lib/davinci_pas_test_kit/jobs/send_pas_subscription_notification.rb +136 -0
- data/lib/davinci_pas_test_kit/jobs/send_subscription_handshake.rb +139 -0
- data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +8 -7
- data/lib/davinci_pas_test_kit/response_generator.rb +397 -0
- data/lib/davinci_pas_test_kit/tags.rb +9 -0
- data/lib/davinci_pas_test_kit/urls.rb +8 -0
- data/lib/davinci_pas_test_kit/user_input_response.rb +11 -8
- data/lib/davinci_pas_test_kit/validation_test.rb +0 -1
- data/lib/davinci_pas_test_kit/version.rb +2 -2
- metadata +30 -14
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +0 -38
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +0 -39
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +0 -35
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +0 -39
- 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
|
-
|
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
|
17
|
-
|
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
|
28
|
-
config options: {
|
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
|
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.
|
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-
|
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.
|
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.
|
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/
|
102
|
-
- lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/
|
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
|