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
@@ -16,27 +16,23 @@ Inferno will simulate a PAS server for the client under test to interact with. T
16
16
  will be expected to initiate requests to the server and demonstrate its ability to react
17
17
  to the returned responses. Over the course of these interactions,
18
18
  Inferno will seek to observe conformant handling of PAS requirements, including
19
- - The ability of the client to initiate and react to
20
- - The approval of a prior authorization request
21
- - The denial of a prior authorization request
22
- - The pending of a prior authorization request and a subsequent final decision
19
+ - The ability of the client to initiate a prior authorization submission and react to
20
+ - The approval of the request
21
+ - The denial of the request
22
+ - The pending of the request and a subsequent notification that a final decision was made
23
23
  - The ability of the client to provide data covering the full scope of required by PAS, including
24
24
  - The ability to send prior auth requests and inquiries with all PAS profiles and all must support elements on
25
25
  those profiles
26
26
  - The ability to handle responses that contain all PAS profiles and all must support elements on those
27
27
  profiles (not included in the current version of these tests)
28
28
 
29
- Because X12 details, including coded values indicating details like “denied” and “pended”,
30
- are not public, Inferno is not currently able to generate a full set of responses to requests
31
- or otherwise provide details on specific codes used to represent those concepts
32
- and is limited to responses that look similar to the examples in the IG (e.g., [here](https://hl7.org/fhir/us/davinci-pas/STU2/Bundle-ReferralAuthorizationResponseBundleExample.html)).
33
- Thus,
34
-
35
- - In order to demonstrate specific workflows, namely pend and denial, users will need to provide the expected
36
- response for Inferno to send back
37
- - The current tests do not return to the client all PAS profiles and must support elements to confirm support.
38
-
39
- For further details on limitations of these tests, see the *Testing Limitations* section below.
29
+ Inferno contains basic logic to generate approval, denial, and pended responses, along with a
30
+ notification that a final decision was made, as a part of the above workflows.
31
+ These responses are based on examples available in the PAS Implementation Guide
32
+ and are conformant, but may not meet the needs of actual implementations. Thus,
33
+ testers may provide Inferno with specific responses for Inferno to echo. If responses
34
+ are provided, Inferno will check them for conformance to ensure that they demonstrate
35
+ a fully conformant exchange.
40
36
 
41
37
  All requests and responses will be checked for conformance to the PAS
42
38
  IG requirements individually and used in aggregate to determine whether
@@ -47,67 +43,204 @@ validated with the Java validator using `tx.fhir.org` as the terminology server.
47
43
 
48
44
  ### Quick Start
49
45
 
50
- For Inferno to simulate a server that always returns approved responses, it needs
46
+ For Inferno to simulate a server that returns mocked conformant responses, it needs
51
47
  only to know the bearer token that the client will send on requests, for which there are two options.
52
48
 
53
- 1. If you want to choose your own bearer token, then
54
- 1. Select the "2. PAS Client Validation" test from the list on the left.
49
+ 1. If you want to choose your own bearer token, then:
50
+ 1. Select the **2.** PAS Client Validation test from the list on the left.
55
51
  2. Click the '*Run All Tests*' button on the right.
56
- 3. In the "access_token" field, enter the bearer token that will be sent by the client under test (as part
57
- of the Authorization header - Bearer: <provided value>).
52
+ 3. In the "Access Token" input field, enter the bearer token that will be sent by the client under test
53
+ in the Authorization HTTP header (format: `Bearer <provided value>`) for all requests to Inferno.
58
54
  4. Click the '*Submit*' button at the bottom of the dialog.
59
- 2. If you want to use a client_id to obtain an access token, then
55
+ 2. If you want to use a Client ID to obtain an access token, then:
60
56
  1. Click the '*Run All Tests*' button on the right.
61
- 2. Provide the client's registered id "client_id" field of the input (NOTE, Inferno doesn't support the
57
+ 2. Provide the client's registered id "Client ID" input field (NOTE: Inferno doesn't support the
62
58
  registration API, so this must be obtained from another system or configured manually).
63
59
  3. Click the '*Submit*' button at the bottom of the dialog.
64
- 4. Make a token request that includes the specified client id to the
65
- `<inferno host>/custom/davinci_pas_client_suite_v201/mock_auth/token` endpoint to get
66
- an access token to use on the request of the requests.
67
-
68
- In either case, the tests will continue from that point, requesting the user to
60
+ 4. Make a token request that includes the specified Client ID to the
61
+ `<inferno host>/custom/davinci_pas_client_suite_v201/mock_auth/token` endpoint to get
62
+ an access token from Inferno which the client will need to provide in the
63
+ Authorization HTTP header (format: `Bearer <provided value>`) for all subsequent
64
+ requests to Inferno for this test session. See the documentation in the
65
+ **1.** Demonstration Authorization test for details on the supported access token request.
66
+
67
+ In either case, the tests will continue from that point, requesting the tester to
69
68
  direct the client to make certain requests to demonstrate PAS client capabilities.
70
69
 
71
70
  Note: authentication options for these tests have not been finalized and are subject to change.
72
71
 
73
- ### Complete Setup
74
-
75
- The *Quick Start* approach does not test pended or deny workflows. To test these, provide a
76
- json-encoded FHIR bundle in the "Claim pended response JSON" and "Claim deny response JSON" input field after
77
- clicking the '*Run All Tests*' button. These responses will be echoed back when a request
78
- is made during the corresponding test.
79
-
80
- Selecting the *Example PAS Server Responses* Preset in the dropdown in the upper left will fill in example
81
- FHIR bundles for the pended and deny responses. It will also fill in a sample value for the Client ID,
82
- which is only necessary for the *Demonstrate Authorization* test group, which can be skipped in favor of
83
- manual bearer token input in subsequent tests as described above.
84
-
85
72
  ### Postman-based Demo
86
73
 
87
74
  If you do not have a PAS client but would like to try the tests out, you can use
88
75
  [this postman collection](https://github.com/inferno-framework/davinci-pas-test-kit/blob/main/config/PAS%20Test%20Kit%20Client%20Test%20Demo.postman_collection.json)
89
- to make requests against Inferno. The following requests and example responses from that collection can be used.
90
-
91
- - Configuration
92
- - *Deny Response* example under the *Homecare Prior Authorization Request*: use as the value of the
93
- "Claim denied response JSON" input field. NOTE: this contains a placeholder code `DENIEDCODE` for the
94
- X12 code representing the denied status. Replace with the X12-specified code before providing to Inferno
95
- to accurately replicate a denial workflow for the client under test.
96
- - *Pend Response* example under the *Medical Services Prior Authorization Request*: use as the value of the
97
- "Claim pended response JSON" input field. NOTE: this contains a placeholder code `PENDEDCODE` for the
98
- X12 code representing the pended status. Replace with the X12-specified code before providing to Inferno
99
- to accurately replicate a pend workflow for the client under test.
100
- - Submissions
101
- - *mock token*: for submitting a client id to get back an access token to use on the rest of the tests. Set the
102
- collection's access_token variable to the result.
103
- - *Referral Prior Authorization Request*: use for the "Approval" workflow test submission.
104
- - *Medical Services Prior Authorization Request*: use for the "Pended" workflow test submission.
105
- - *Medical Services Inquiry Request*: use for the "Pended" workflow test inquiry.
106
-
107
- No additional requests are present to submit as a part of the must support tests, so
108
- testers can simply click the link to indicate they are done submitting requests. Note
109
- that the requests within the Postman client are not expected to fully pass the tests as they
110
- do not contain all must support items.
76
+ to make requests against Inferno and see the mocked responses provided by Inferno. To use, load
77
+ the collection into the [Postman app](https://www.postman.com/downloads/) and follow these steps:
78
+
79
+ 1. Select the *PAS Client Test Suite Demo* Collection in postman and go to the "Variables" tab
80
+ (see the Overview tab for more details on what the variables control).
81
+ 1. Note the "Current value" of the **client_id** variable for use in configuring Inferno. Update it
82
+ to another value to use instead, if desired.
83
+ 1. Start a Da Vinci PAS Client Suite v2.0.1 session from the [PAS Test Kit page on
84
+ inferno.healthit.gov](https://inferno.healthit.gov/test-kits/davinci-pas/).
85
+ 1. Click the *Run All Tests* button in the upper right hand corner of the suite.
86
+ 1. In the **Client ID** input, enter the value from the **client_id** Postman variable and click the
87
+ *Submit* button.
88
+ 1. When the "User Action" dialog appears, return to Postman and change to the Authorization tab. Scroll down
89
+ to find the *Get New Access Token* button at the bottom and click it.
90
+ 1. When a success message appears, click the *Proceed* button and then the *Use Token* button.
91
+ 1. Back in Inferno, a new "User Action" dialog will appear requesting a Subscription. When it does, return to Postman,
92
+ open the "Create Subscription Request" entry under the "Subscription Setup" folder in the collection,
93
+ and click the *Send* button.
94
+ 1. Back in Inferno, an **Approval Workflow Test** "User Action" will appear. When it does, return to Postman,
95
+ open the "Prior Auth Request For Approval" entry under the "Approval Workflow Requests" folder in the
96
+ collection, and click the *Send* button.
97
+ 1. Back in Inferno, an attestation "User Action" will appear asking you to confirm that the prior auth
98
+ request is listed as approved in the client app based on Inferno's response to the request. Search
99
+ in the response returned to Postman for the string "Certified in total" which indicates the prior
100
+ auth request was approved and click the link in Inferno indicating the attestation statement is true.
101
+ 1. Next, a **Denial Workflow Test** "User Action" will appear. When it does, return to Postman,
102
+ open the "Prior Auth Request For Denial" entry under the "Denial Workflow Requests" folder in the
103
+ collection, and click the *Send* button.
104
+ 1. Back in Inferno, an attestation "User Action" will appear asking you to confirm that the prior auth
105
+ request is listed as denied in the client app based on Inferno's response to the request. Search
106
+ in the response returned to Postman for the string "Not Certified" which indicates the prior
107
+ auth request was denied and click the link in Inferno indicating the attestation statement is true.
108
+ 1. Next, a **Pended Workflow Test** "User Action" will appear. When it does, return to Postman,
109
+ open the "Prior Auth Request For Pended" entry under the "Pended Workflow Requests" folder in the
110
+ collection, and click the *Send* button.
111
+ 1. Search in the response returned to Postman for the string "Pending" which indicates the prior
112
+ auth request was pended and a final decision will be made later. You'll use this information in
113
+ a later attestation.
114
+ 1. Meanwhile, Inferno sent a notification indicating that a final decision was made (5-10 seconds
115
+ after the prior auth request). If interested in seeing the notification message, it can be found
116
+ on the [notifications
117
+ page](https://subscriptions.argo.run/subscriptions/notifications-received?store=r4) for the
118
+ [Argonaut Subscriptions Reference Implementation](https://subscriptions.argo.run/), which
119
+ hosts a notification endpoint that is used to receive Subscription notifications for this demo.
120
+ Note that when looking for recent notifications, **Received** timestamps are in UTC which is
121
+ 5 hours ahead of Eastern Standard Time (4 hours ahead of Eastern Daylight Time).
122
+ 1. Return to Postman, open the "Prior Auth Inquiry For Pended" entry under the "Pended Workflow Requests"
123
+ folder in the collection, and click the *Send* button.
124
+ 1. Search in the response returned to Postman for the string "Certified in total" which indicates the prior
125
+ auth request was approved. You'll use this information in a later attestation.
126
+ 1. Return to Inferno, scroll down in the "User Action" dialog and click the "click here to complete the test"
127
+ link to allow Inferno to evaluate the pended workflow.
128
+ 1. Two attestations will appear, the first stating that the prior auth request was registered in the
129
+ client as pended and that it was subsequently finalized. You checked these above and can use the
130
+ true link for both.
131
+ 1. Two additional "User Action" dialogs will appear requesting additional `$submit` and `$inquire`
132
+ requests to demonstrate must support elements. This demo does not have any additional requests
133
+ and does not attempt to demonstrate all must support elements, so click the link to indicate
134
+ you are done submitting requests for each. Note that requests submitted during the workflow section
135
+ will be evaluated and you can inspect the results under test **3.2** *Demonstrate Element Support*
136
+ to see both passing and failing tests.
137
+ 1. Once Inferno finishes evaluating the requests, the test will complete allowing you to review the
138
+ results, including warning and error messages as well as requests associated with each test.
139
+
140
+ #### Optional Demo Modifications
141
+
142
+ This demo uses `id-only` notifications for Pended workflow. To see a demonstration of `full-resource`
143
+ notifications, replace the string `id-only` in the "Create Subscription Request" entry under the
144
+ "Subscription Setup" folder in the collection with the string `full-resource` (found in an extension
145
+ under the `_payload` element).
146
+
147
+ ## Response and Notification Content
148
+
149
+ To assist in testers getting started with the PAS Client tests quickly, Inferno will generate
150
+ conformant mocked `$submit` and `$inquire` operation responses and Subscription notifications.
151
+ However, the simple mocked messages may not drive the workflows of real systems in a way that allows
152
+ them to demonstrate their implementation of the PAS specification. Thus, Inferno also allows each message
153
+ returned or initiated by Inferno to be specified by the tester. These messages must themselves be
154
+ conformant to PAS specification requirements and additional test requirements in order for a test run to
155
+ serve as a demonstration of a conformant implementation.
156
+
157
+ The rest of this section provides details on how Inferno determines the content to use in responses
158
+ and notifications.
159
+
160
+ ### Inferno Modifications of Tester-provided Responses and Notifications
161
+
162
+ Requests provided by testers will be modified by Inferno to try and populate details that testers won't
163
+ know ahead of time. These modifications fall into two categories:
164
+ - **Timestamps**: creation timestamps, such as those on Bundles, ClaimResponses, and event notifications,
165
+ will be updated or populated by Inferno so that they are in sync with the time the message is sent.
166
+ - **Resource Ids**: some resource ids will not be known ahead of time and will be added or updated by Inferno
167
+ including
168
+ - *Claim Id*: if the tester provides a `$submit` or `$inquire` response with `ClaimResponse.request` populated,
169
+ then Inferno will update it with the fullUrl of the Claim provided in the request. This avoids the need for
170
+ testers to know the Claim Id ahead of time which may be difficult for some systems.
171
+ - *ClaimResponse Id*: if the tester provides a Notification but has Inferno generate the `$submit` response,
172
+ then Inferno will update the focus to use the ClaimResponse id that it generates.
173
+
174
+ If the tester provides an input that is malformed in some way such that Inferno cannot get the details
175
+ that Inferno needs to make the modifications, then the raw input will be used.
176
+
177
+ ### Response and Notification Correspondence Requirements
178
+
179
+ Beyond the minor modifications described above, Inferno does not modify provided content to ensure that
180
+ they are consistent with each other or the time they are executed. For example, in the pended
181
+ workflow, it is up to the tester to ensure that if they provide responses for the `$submit` and `$inquire`
182
+ operations that they share whatever details, such as identifiers, needed to connect them together and drive
183
+ the workflow in their system. Timestamps not associated with messaging time such as when a prior authorization
184
+ response is valid are also not modified by Inferno. Unlike details that Inferno modifies as described above,
185
+ testers should have control over and/or knowledge of the necessary details and values to construct consistent
186
+ and working messages for Inferno to use.
187
+
188
+ ### Tester-provided Response and Notification Inputs
189
+
190
+ The following test inputs control Inferno messaging behavior:
191
+ - **Claim approved response JSON**: If populated, this is used in the **2.2.1** "Demonstrate Approval Workflow" tests
192
+ to respond to `$submit` requests. The response needs to indicate to the system that the prior auth request has
193
+ been approved.
194
+ - **Claim denied response JSON**: If populated, this used in the **2.2.2** "Demonstrate Denial Workflow" tests
195
+ to respond to `$submit` requests. The response needs to indicate to the system that the prior auth request has
196
+ been denied.
197
+ - **Claim pended response JSON**: If populated, this used in the **2.2.3** "Demonstrate Pended Workflow" tests
198
+ to respond to `$submit` requests. The response needs to indicate to the system that the prior auth request has
199
+ been pended.
200
+ - **Claim updated notification JSON**: If populated, this used in the **2.2.3** "Demonstrate Pended Workflow" tests
201
+ as the event notification sent for the Subscription indicating that a decision has been finalized for the
202
+ pended prior auth request. The content of the notification needs to match the details of the Subscription
203
+ provided in the **2.1** "Subscription Setup" tests.
204
+ - **Inquire approved response JSON**: If populated, this used in the **2.2.3** "Demonstrate Pended Workflow"
205
+ tests to respond to `$inquire` requests. The response needs to indicate to the system that the
206
+ prior auth request has been approved.
207
+
208
+ ### Generation Logic
209
+
210
+ When generating responses and notifications, Inferno uses the following logic. These conform to the
211
+ requirements of the PAS specification, but may not make sense in an actual workflow.
212
+ - **`$submit` and `$inquire` responses**: these responses are created mostly from the incoming request, specific details include:
213
+ - The Patient, insurer Organization, and requestor entity instances are pulled into the response
214
+ Bundle and referenced in the `patient`, `insurer`, and `requestor` elements respectively.
215
+ Note that get found by following references found in the submitted Claim instance. If relative
216
+ references are used in the Claim, the Claim entry `fullUrl` needs to be a absolute reference
217
+ and not a UUID, else the entries won't get pulled in correctly.
218
+ - In the ClaimResponse, the `identifier`, `type`, `status`, and `use` elements are pulled
219
+ in from the Claim in the request.
220
+ - The `Bundle.timestamp` and `ClaimResponse.created` timestamps are populated using the current time.
221
+ - The `ClaimResponse.outcome` is hardcoded to `complete`.
222
+ - For each `item` entry in the request Claim, a `ClaimResponse.item` entry is created with the
223
+ `itemSequence` value copied over, `itemPreAuthIssueDate` and `itemPreAuthPeriod` extensions
224
+ added using the current date and a month starting on the current date respectively,
225
+ and an adjudication entry with a `category` of `submitted` that contains the `reviewAction` extension with a
226
+ `reviewActionCode` that matches the current workflow: `A1` ("Certified in total") for approval,
227
+ `A3` ("Not Certified") for denial, and `A4` ("Pending") for pending.
228
+ - **Notification Bundle**: Inferno supports mocking both `id-only` and `full-resource` notifications.
229
+ The following details are relevant:
230
+ - Inferno pulls in details from the Subscription created for the test session to use in
231
+ creating the Notification, including the `topic` and the `subscription` reference.
232
+ - Inferno hardcodes the `status` as `active` and the `type` as `event-notification`.
233
+ - Inferno will always include a single `notification-event` entry with a `timestamp` of the current
234
+ time and with a `focus` that points to the ClaimResponse returned on the `$submit`.
235
+ If it cannot find the ClaimResponse reference from the `$submit` response returned
236
+ by Inferno, it will generate a random UUID (which isn't likely to work correctly when received).
237
+ - The subscription's `events-since-subscription-start` and the event's `event-number` will always
238
+ be 1 as Inferno is not able to track notifications across multiple sessions or runs.
239
+ - When generating a `full-resource` notification, Inferno will include `additional-context`
240
+ references for each entry in the `$submit` response Bundle other than the ClaimResponse
241
+ (which is already in the `focus`). Then it will include Notification Bundle entries for
242
+ each instance in the `$submit` response Bundle, including the ClaimResponse, with `reviewActionCode`
243
+ extensions updated to indicate approval using code `A1` ("Certified in total") for approval.
111
244
 
112
245
  ## Testing Limitations
113
246
 
@@ -136,26 +269,34 @@ top of the [IG home page](https://hl7.org/fhir/us/davinci-pas/STU2/index.html):
136
269
  The implications of this reliance on proprietary information that is not publicly available means that this test
137
270
  kit:
138
271
 
139
- - Cannot verify the correct usage of X12-based terminology: terminology requirements for all elements bound to X12
272
+ - *Cannot verify the correct usage of X12-based terminology*: terminology requirements for all elements bound to X12
140
273
  value sets will not be validated.
141
- - Cannot verify the meaning of codes: validation that a given response conveys something specific, e.g., approval
274
+ - *Cannot verify the meaning of codes*: validation that a given response conveys something specific, e.g., approval
142
275
  or pending, is not performed.
143
- - Cannot verify matching semantics on inquiries: no checking of the identity of the ClaimResponse returned for an
276
+ - *Cannot verify matching semantics on inquiries*: no checking of the identity of the ClaimResponse returned for an
144
277
  inquiry, e.g., that it matches the input or the original request.
145
278
 
146
279
  These limitations may be removed in future versions of these tests. In the meantime, testers should consider these
147
280
  requirements to be verified through attestation and should not represent their systems to have passed these tests
148
281
  if these requirements are not met.
149
282
 
150
- ### Underspecified Subscription Details
283
+ ### Subscription Details
284
+
285
+ Subscription details in the PAS 2.0.1 specification are relatively underspecified and the
286
+ [published 2.1.0 version of the
287
+ specification](https://hl7.org/fhir/us/davinci-pas/STU2.1/specification.html#subscription)
288
+ makes significant changes, including requiring `full-resource` and updating details such as
289
+ the filter criteria.
151
290
 
152
- The current PAS specification around subscriptions and notifications
153
- is not detailed enough to support testing. Notably,
154
- - There are no examples of what a notification payload looks like
155
- - There is no instruction on how to turn the notification payload into an inquiry
291
+ Based on the immaturity of the 2.0.1 requirements around Subscriptions, these tests implement and check for
292
+ the mechanics of Subscriptions and notifications, but do not look closely at the details. For example,
293
+ `id-only` and `full-resource` are supported and the filter criteria format is not checked. Future versions
294
+ of these tests may be more stringent.
156
295
 
157
- Once these details have been clarified, validation of the notification workflows
158
- will be added to these tests.
296
+ Additionally, to test Subscriptions and pending workflows, a new Subscription must be created for each
297
+ test session, which may require testers to re-initialize previously-created Subscriptions. Future versions
298
+ of these tests may relax this requirement and feedback on whether this would reduce burden and how this
299
+ might look are welcome.
159
300
 
160
301
  ### Future Details
161
302
 
@@ -167,7 +308,7 @@ The PAS IG places additional requirements on clients that are not currently test
167
308
  - US Core profile support for supporting information
168
309
  - The ability to handle responses containing all PAS-defined profiles and must support elements
169
310
  - Most details requiring manual review of the client system, e.g., the requirement that clinicians can update
170
- details of the prior authorization request before submitting them
311
+ details of the prior authorization request before submitting them
171
312
 
172
313
  These and any other requirements found in the PAS IG may be tested in future versions of these tests.
173
314
 
@@ -1,25 +1,54 @@
1
1
  require_relative '../user_input_response'
2
+ require_relative '../response_generator'
3
+ require_relative '../urls'
4
+ require_relative '../jobs/send_pas_subscription_notification'
5
+ require 'subscriptions_test_kit'
2
6
 
3
7
  module DaVinciPASTestKit
4
8
  class ClaimEndpoint < Inferno::DSL::SuiteEndpoint
9
+ include SubscriptionsTestKit::SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
10
+ include ResponseGenerator
11
+ include URLs
12
+
13
+ # override the one from URLs
14
+ def suite_id
15
+ request.path.split('/custom/')[1].split('/')[0] # request.path = {base inferno path}/custom/{suite_id}/...
16
+ end
17
+
5
18
  def test_run_identifier
6
19
  request.headers['authorization']&.delete_prefix('Bearer ')
7
20
  end
8
21
 
9
22
  def tags
10
- [operation == 'submit' ? SUBMIT_TAG : INQUIRE_TAG]
23
+ operation_tag = operation == 'submit' ? SUBMIT_TAG : INQUIRE_TAG
24
+ workflow_tag = WORKFLOW_TAG_MAP[workflow]
25
+
26
+ return [operation_tag, workflow_tag] if workflow_tag.present?
27
+
28
+ [operation_tag]
29
+ end
30
+
31
+ def workflow
32
+ case test.id
33
+ when /.*pended.*/
34
+ :pended
35
+ when /.*denial.*/
36
+ :denial
37
+ when /.*approval.*/
38
+ :approval
39
+ end
11
40
  end
12
41
 
42
+ WORKFLOW_TAG_MAP = {
43
+ pended: PENDED_WORKFLOW_TAG,
44
+ denial: DENIAL_WORKFLOW_TAG,
45
+ approval: APPROVAL_WORKFLOW_TAG
46
+ }.freeze
47
+
13
48
  def make_response
14
49
  response.status = 200
15
50
  response.format = :json
16
51
 
17
- user_inputted_response = UserInputResponse.user_inputted_response(test, result)
18
- if user_inputted_response.present?
19
- response.body = user_inputted_response
20
- return
21
- end
22
-
23
52
  req_bundle = FHIR.from_contents(request.body.string)
24
53
  claim_entry = req_bundle&.entry&.find { |e| e&.resource&.resourceType == 'Claim' }
25
54
  claim_full_url = claim_entry&.fullUrl
@@ -28,27 +57,26 @@ module DaVinciPASTestKit
28
57
  return
29
58
  end
30
59
 
31
- root_url = base_url(claim_full_url)
32
- claim_response = mock_claim_response(claim_entry.resource, req_bundle, root_url)
33
-
34
- res_bundle = FHIR::Bundle.new(
35
- id: SecureRandom.uuid,
36
- meta: FHIR::Meta.new(profile: if operation == 'submit'
37
- 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-pas-response-bundle'
38
- else
39
- 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-pas-inquiry-response-bundle'
40
- end),
41
- timestamp: Time.now.utc.iso8601,
42
- type: 'collection',
43
- entry: [
44
- FHIR::Bundle::Entry.new(fullUrl: "urn:uuid:#{claim_response.id}",
45
- resource: claim_response)
46
- ]
47
- )
48
-
49
- res_bundle.entry.concat(referenced_entities(claim_response, req_bundle.entry, root_url))
50
-
51
- response.body = res_bundle.to_json
60
+ user_inputted_response = UserInputResponse.user_inputted_response(test, operation, result)
61
+ if user_inputted_response.present?
62
+ generated_claim_response_uuid = nil
63
+ response_bundle_json = update_tester_provided_response(user_inputted_response, claim_full_url)
64
+ else
65
+ decision = # always use the workflow, except for pended when the inquire will get approved
66
+ if operation == 'inquire' && workflow == :pended
67
+ :approval
68
+ else
69
+ workflow
70
+ end
71
+ generated_claim_response_uuid = SecureRandom.uuid
72
+ response_bundle_json = mock_response_bundle(req_bundle, operation, decision, generated_claim_response_uuid)
73
+ end
74
+
75
+ response.body = response_bundle_json
76
+
77
+ return unless workflow == :pended && operation == 'submit'
78
+
79
+ start_notification_job(response_bundle_json, :approval, generated_claim_response_uuid)
52
80
  end
53
81
 
54
82
  def update_result
@@ -57,78 +85,6 @@ module DaVinciPASTestKit
57
85
 
58
86
  private
59
87
 
60
- # Note that references from the claim to other resources in the bundle need to be changed to absolute URLs
61
- # if they are relative, because the ClaimResponse's fullUrl is a urn:uuid
62
- #
63
- # @private
64
- def mock_claim_response(claim, bundle, root_url)
65
- return FHIR::ClaimResponse.new(id: SecureRandom.uuid) if claim.blank?
66
-
67
- now = Time.now.utc
68
-
69
- FHIR::ClaimResponse.new(
70
- id: SecureRandom.uuid,
71
- meta: FHIR::Meta.new(profile: if operation == 'submit'
72
- 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claimresponse'
73
- else
74
- 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claiminquiryresponse'
75
- end),
76
- identifier: claim.identifier,
77
- type: claim.type,
78
- status: claim.status,
79
- use: claim.use,
80
- patient: absolute_reference(claim.patient, bundle.entry, root_url),
81
- created: now.iso8601,
82
- insurer: absolute_reference(claim.insurer, bundle.entry, root_url),
83
- requestor: absolute_reference(claim.provider, bundle.entry, root_url),
84
- outcome: 'complete',
85
- item: claim.item.map do |item|
86
- FHIR::ClaimResponse::Item.new(
87
- extension: [
88
- FHIR::Extension.new(
89
- url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthIssueDate',
90
- valueDate: now.strftime('%Y-%m-%d')
91
- ),
92
- FHIR::Extension.new(
93
- url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthPeriod',
94
- valuePeriod: FHIR::Period.new(start: now.strftime('%Y-%m-%d'),
95
- end: (now + 1.month).strftime('%Y-%m-%d'))
96
- )
97
- ],
98
- itemSequence: item.sequence,
99
- adjudication: [
100
- FHIR::ClaimResponse::Item::Adjudication.new(
101
- extension: [
102
- FHIR::Extension.new(
103
- url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewAction',
104
- extension: [
105
- FHIR::Extension.new(
106
- url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewActionCode',
107
- valueCodeableConcept: FHIR::CodeableConcept.new(
108
- coding: [
109
- FHIR::Coding.new(
110
- system: 'https://codesystem.x12.org/005010/306',
111
- code: 'A1',
112
- display: 'Certified in total'
113
- )
114
- ]
115
- )
116
- )
117
- ]
118
- )
119
- ],
120
- category: FHIR::CodeableConcept.new(
121
- coding: [
122
- FHIR::Coding.new(system: 'http://terminology.hl7.org/CodeSystem/adjudication', code: 'submitted')
123
- ]
124
- )
125
- )
126
- ]
127
- )
128
- end
129
- )
130
- end
131
-
132
88
  def handle_missing_required_elements(claim_entry, response)
133
89
  response.status = 400
134
90
  details = if claim_entry.blank?
@@ -146,49 +102,44 @@ module DaVinciPASTestKit
146
102
  request.url&.split('$')&.last
147
103
  end
148
104
 
149
- # Drop the last two segments of a URL, i.e. the resource type and ID of a FHIR resource
150
- # e.g. http://example.org/fhir/Patient/123 -> http://example.org/fhir
151
- # @private
152
- def base_url(url)
153
- return unless url&.start_with?('http://', 'https://')
105
+ def start_notification_job(response_bundle_json, decision, generated_claim_response_uuid)
106
+ notification_bearer_token = client_access_token_input(result)
107
+ notification_contents = notification_json(response_bundle_json, decision, generated_claim_response_uuid)
154
108
 
155
- # Drop everything after the second to last '/', ignoring a trailing slash
156
- url.sub(%r{/[^/]*/[^/]*(/)?\z}, '')
109
+ Inferno::Jobs.perform(Jobs::SendPASSubscriptionNotification, test_run.id, test_run.test_session_id, result.id,
110
+ notification_bearer_token, notification_contents, test_run_identifier, suite_id)
157
111
  end
158
112
 
159
- def referenced_entities(resource, entries, root_url)
160
- matches = []
161
- attributes = resource&.source_hash&.keys
162
- attributes.each do |attr|
163
- value = resource.send(attr.to_sym)
164
- if value.is_a?(FHIR::Reference) && value.reference.present?
165
- match = find_matching_entry(value.reference, entries, root_url)
166
- if match.present? && matches.none?(match)
167
- value.reference = match.fullUrl
168
- matches.concat([match], referenced_entities(match.resource, entries, root_url))
169
- end
170
- elsif value.is_a?(Array) && value.all? { |elmt| elmt.is_a?(FHIR::Model) }
171
- value.each { |val| matches.concat(referenced_entities(val, entries, root_url)) }
172
- end
173
- end
174
-
175
- matches
176
- end
113
+ def notification_json(response_bundle_json, decision, generated_claim_response_uuid)
114
+ user_inputted_notification_json =
115
+ JSON.parse(result.input_json).find { |i| i['name'] == 'notification_bundle' }['value']
177
116
 
178
- def absolute_reference(ref, entries, root_url)
179
- url = find_matching_entry(ref&.reference, entries, root_url)&.fullUrl
180
- ref.reference = url if url
181
- ref
117
+ if user_inputted_notification_json.present?
118
+ update_tester_provided_notification(user_inputted_notification_json, generated_claim_response_uuid)
119
+ else
120
+ generate_notification(response_bundle_json, decision)
121
+ end
182
122
  end
183
123
 
184
- def find_matching_entry(ref, entries, root_url = '')
185
- ref = "#{root_url}/#{ref}" if relative_reference?(ref) && root_url&.present?
124
+ def generate_notification(response_bundle_json, decision)
125
+ subscription = find_subscription(test_run.test_session_id, as_json: true)
126
+ subscription_reference = "#{fhir_subscription_url}/#{subscription['id']}"
127
+ subscription_topic = subscription['criteria']
186
128
 
187
- entries&.find { |entry| entry&.fullUrl == ref }
129
+ if find_subscription_content_type(subscription) == 'full-resource'
130
+ mock_full_resource_notification_bundle(response_bundle_json, subscription_reference, subscription_topic,
131
+ decision)
132
+ else # assume id-only since empty not allowed - if asked for empty, other failures will occur
133
+ mock_id_only_notification_bundle(response_bundle_json, subscription_reference, subscription_topic)
134
+ end
188
135
  end
189
136
 
190
- def relative_reference?(ref)
191
- ref&.count('/') == 1
137
+ def find_subscription_content_type(subscription)
138
+ content_ext = subscription['channel']['_payload']['extension']
139
+ .find do |ext|
140
+ ext['url'] == 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-payload-content'
141
+ end
142
+ content_ext['valueCode'] if content_ext.present?
192
143
  end
193
144
  end
194
145
  end