davinci_dtr_test_kit 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/davinci_dtr_test_kit/auth_groups/oauth2_authentication_group.rb +51 -0
- data/lib/davinci_dtr_test_kit/auth_groups/token_request_test.rb +25 -0
- data/lib/davinci_dtr_test_kit/auth_groups/token_validation_test.rb +13 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group.rb +20 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_questionnaire_response_save_test.rb +31 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +89 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_attestation_test.rb +29 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_override_attestation_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/rendering_enabled_questions_attestation_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +19 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_package_group.rb +16 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +32 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_group.rb +14 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_group.rb +23 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_save_test.rb +31 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_smart_app_questionnaire_workflow_group.rb +22 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_test.rb +36 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_validation_test.rb +35 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +28 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/cql_test.rb +387 -0
- data/lib/davinci_dtr_test_kit/docs/dtr_payer_server_suite_description_v201.md +127 -0
- data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +118 -0
- data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +55 -0
- data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +39 -0
- data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +104 -0
- data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +180 -0
- data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +85 -0
- data/lib/davinci_dtr_test_kit/ext/inferno_core/record_response_route.rb +98 -0
- data/lib/davinci_dtr_test_kit/ext/inferno_core/request.rb +19 -0
- data/lib/davinci_dtr_test_kit/ext/inferno_core/runnable.rb +35 -0
- data/lib/davinci_dtr_test_kit/fixture_loader.rb +99 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_burrito.json +170 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_hamburger.json +175 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_initial.json +140 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json +95 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_dinner_order_static.json +283 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_response_dinner_order_static.json +170 -0
- data/lib/davinci_dtr_test_kit/fixtures/pre_populated_questionnaire_response.json +581 -0
- data/lib/davinci_dtr_test_kit/fixtures/questionnaire_package.json +2121 -0
- data/lib/davinci_dtr_test_kit/fixtures.rb +65 -0
- data/lib/davinci_dtr_test_kit/mock_ehr.rb +72 -0
- data/lib/davinci_dtr_test_kit/mock_payer.rb +142 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +20 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +20 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +88 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_test.rb +41 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +44 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +40 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +42 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +49 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +61 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +17 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +43 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_static_group.rb +51 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +20 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_test.rb +33 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +46 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +50 -0
- data/lib/davinci_dtr_test_kit/tags.rb +8 -0
- data/lib/davinci_dtr_test_kit/urls.rb +50 -0
- data/lib/davinci_dtr_test_kit/validation_test.rb +72 -0
- data/lib/davinci_dtr_test_kit/version.rb +5 -0
- data/lib/davinci_dtr_test_kit.rb +4 -0
- metadata +132 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
The Da Vinci DTR Test Kit Payer Server Suite validates the conformance of payer server
|
|
2
|
+
systems to the STU 2 version of the HL7® FHIR®
|
|
3
|
+
[Da Vinci Documentation Templates and Rules (DTR) Implementation Guide](https://hl7.org/fhir/us/davinci-dtr/STU2/).
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
These tests are a **DRAFT** intended to allow payer implementers to perform
|
|
8
|
+
preliminary checks of their systems against DTR IG requirements and [provide
|
|
9
|
+
feedback](https://github.com/inferno-framework/davinci-dtr-test-kit/issues)
|
|
10
|
+
on the tests. Future versions of these tests may validate other
|
|
11
|
+
requirements and may change the test validation logic.
|
|
12
|
+
|
|
13
|
+
## Test Methodology
|
|
14
|
+
|
|
15
|
+
Inferno will simulate a DTR client application (SMART client or Full EHR) that
|
|
16
|
+
is making requests for questionnaires for the server under test to interact with.
|
|
17
|
+
The server will be expected to respond to these requests made by Inferno. Over the
|
|
18
|
+
course of these interactions, Inferno will seek to observe conformant handling of
|
|
19
|
+
DTR [requirements](https://hl7.org/fhir/us/davinci-dtr/STU2/specification.html#defining-questionnaires)
|
|
20
|
+
around standard questionnaires, adaptive questionnaires, or both.
|
|
21
|
+
|
|
22
|
+
In terms of the validation performed on the returned questionnaires these
|
|
23
|
+
tests assume that payers are not required to support specific features, but only those
|
|
24
|
+
that they need in order to implement their forms. The DTR IG does [require that](https://hl7.org/fhir/us/davinci-dtr/STU2/specification.html#population)
|
|
25
|
+
"Questionnaires SHALL include logic that supports population from the EHR where possible"
|
|
26
|
+
and puts some restrictions on how Questionnaire features can be used. Thus, these
|
|
27
|
+
tests require demonstration of pre-population features and validates conformant
|
|
28
|
+
use of Questionnaire features, but don't currently require demonstration of all
|
|
29
|
+
Questionnnaire Must Support elements.
|
|
30
|
+
|
|
31
|
+
Because the business logic that determines which questionnaires are returned
|
|
32
|
+
is outside of the DTR specification and will vary between implementers, testers
|
|
33
|
+
are required to provide the requests that Inferno will make to the server, either
|
|
34
|
+
by providing the requests to make up-front, or by sending them to Inferno during
|
|
35
|
+
test execution using a tester-controlled client.
|
|
36
|
+
|
|
37
|
+
All responses returned by the server, as well as tester-provided requests, will be checked
|
|
38
|
+
for conformance to the DTR IG requirements individually and used in aggregate to determine
|
|
39
|
+
whether required features and functionality are present. HL7® FHIR® resources are
|
|
40
|
+
validated with the Java validator using `tx.fhir.org` as the terminology server.
|
|
41
|
+
|
|
42
|
+
## Running the Tests
|
|
43
|
+
|
|
44
|
+
### Quick Start
|
|
45
|
+
|
|
46
|
+
Execution of these tests require a significant amount of tester input in the
|
|
47
|
+
form of requests that Inferno will make against the server under test. If
|
|
48
|
+
you don't have a server or know specific requests that will elicit representative
|
|
49
|
+
questionnaires from your server, see the *Sample Execution* section below.
|
|
50
|
+
|
|
51
|
+
Otherwise, the requests for Inferno to make can be provided in one of two ways:
|
|
52
|
+
1. Statically within the Inferno inputs: provide the json request bodies when running
|
|
53
|
+
the tests. This approach works very well for testing standard (static) questionnaires
|
|
54
|
+
where there is only one request for Inferno to make (input = *Initial Static Questionnaire Request*). It is less ideal for adaptive
|
|
55
|
+
questionnaires as a sequence of `$next-question` requests (inputs = *Initial Adaptive Questionnaire Request* and *Next Question Requests*) is required, which is provided as a json list of
|
|
56
|
+
request body objects.
|
|
57
|
+
2. Dynamically during test execution: use a tester-controlled client to provide requests to
|
|
58
|
+
Inferno while the tests are running. At points that Inferno needs to make a request, execution
|
|
59
|
+
will pause while the request is provided from the client. Inferno uses a bearer token
|
|
60
|
+
provided in the test inputs (input = *Access Token* for the DTR Client Flow (not the
|
|
61
|
+
one under the *OAuth Credentials* input)) to identify these requests. The provided token
|
|
62
|
+
must be sent as a part of the string `Bearer <token>` within the `Authentication` header of
|
|
63
|
+
all requests. When Inferno receives a request from the tester-controlled client, it will
|
|
64
|
+
send that to the server under test and then return the response back to the client so that
|
|
65
|
+
the client's workflow, e.g., around an adaptive questionnaire can continue.
|
|
66
|
+
|
|
67
|
+
In addition to the above configuration needed for identification of tests, the following additional
|
|
68
|
+
inputs are required
|
|
69
|
+
- *Questionnaire Retrieval Method*: indicate whether only static, only adaptive, or both types
|
|
70
|
+
of questionnaires will be tested.
|
|
71
|
+
- *FHIR Server Base Url*: the location of the server to test
|
|
72
|
+
- *OAuth Credentials*: if the server under test requires authentication, provide those details
|
|
73
|
+
here.
|
|
74
|
+
|
|
75
|
+
For more details on additional inputs that may be needed, see the *Additional Configuration Details*
|
|
76
|
+
section below.
|
|
77
|
+
|
|
78
|
+
### Sample Execution
|
|
79
|
+
|
|
80
|
+
If you would like to try out the tests but don't have a DTR payer server implementation,
|
|
81
|
+
you can run these tests against the DTR SMART Client test suite included in this test kit
|
|
82
|
+
using the following steps:
|
|
83
|
+
1. Start an Inferno session of the Da Vinci DTR SMART App Test Suite.
|
|
84
|
+
1. Select test 2.1.1 *Static Questionnaire Workflow* from the menu on the left.
|
|
85
|
+
1. Click the "Run All Tests" button in the upper right.
|
|
86
|
+
1. In the "access_token" input, put `cnVuIHRvZ2V0aGVy`.
|
|
87
|
+
1. Click the "submit" button in the dialog that appears. The client tests will now be waiting for requests.
|
|
88
|
+
1. Start an Inferno session of the DTR Payer Server test suite.
|
|
89
|
+
1. Select test 1 *Static Questionnaire Package Retrieval* from the menu on the left.
|
|
90
|
+
1. Select the *Run Against DTR SMART App Tests* option from the Preset dropdown in the
|
|
91
|
+
upper left.
|
|
92
|
+
1. Click the "Run All Tests" button in the upper right.
|
|
93
|
+
1. Click the "submit" button in the dialog that appears. The server tests will now make requests
|
|
94
|
+
against the client test session, which will respond with a static questionnaire that the
|
|
95
|
+
these server tests can validate.
|
|
96
|
+
|
|
97
|
+
At this time, only the standard questionnaire functionality can be tested using this approach as
|
|
98
|
+
the client tests have not implemented an adaptive questionnaire set of tests.
|
|
99
|
+
|
|
100
|
+
## Additional Configuration Details
|
|
101
|
+
|
|
102
|
+
The details provided here supplement the documentation of individual fields in the input dialog
|
|
103
|
+
that appears when initiating a test run.
|
|
104
|
+
|
|
105
|
+
### Custom Endpoint for Accessing a Particular Resource
|
|
106
|
+
|
|
107
|
+
If the `$questionnaire-package` requests should be made to a location other than
|
|
108
|
+
`/Questionnaire/$questionnaire-package` under the base server URL, enter the
|
|
109
|
+
location the requests should be made relative to the base server URL.
|
|
110
|
+
|
|
111
|
+
## Limitations
|
|
112
|
+
|
|
113
|
+
These tests currently require the server under test to demonstrate a single example of
|
|
114
|
+
a conformant standard (static) and / or an adaptive questionnaire. This is based
|
|
115
|
+
on the interpretation of the DTR IG as allowing payers to choose the features that
|
|
116
|
+
they want to support. If this interpretation turns out to be inconsistent with the
|
|
117
|
+
intention of the IG authors then future versions of the tests may require the payer
|
|
118
|
+
to provide additional examples.
|
|
119
|
+
|
|
120
|
+
The payer responses are also tested to ensure that appropriate libraries and expressions are
|
|
121
|
+
included to faciliate pre-population of questionnaires. The following is not tested:
|
|
122
|
+
- CQL is version 1.5
|
|
123
|
+
- CQL is valid and executed to populate the questionnaire
|
|
124
|
+
- CQL has a context of “Patient”
|
|
125
|
+
- CQL definitions and variables defined on ancestor elements or preceding expression extensions within the same
|
|
126
|
+
Questionnaire item are in scope for referencing in descendant/following expressions.
|
|
127
|
+
- Within Expression elements, the base expression CQL SHALL be accompanied by a US Public Health Alternative Expression Extension containing the compiled JSON ELM for the expression.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
The Da Vinci DTR Test Kit SMART App Suite validates the conformance of SMART apps
|
|
2
|
+
to the STU 2 version of the HL7® FHIR®
|
|
3
|
+
[Da Vinci Documentation Templates and Rules (DTR) Implementation Guide](https://hl7.org/fhir/us/davinci-dtr/STU2/).
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
These tests are a **DRAFT** intended to allow app implementers to perform
|
|
8
|
+
preliminary checks of their systems against DTR IG requirements and [provide
|
|
9
|
+
feedback](https://github.com/inferno-framework/davinci-dtr-test-kit/issues)
|
|
10
|
+
on the tests. Future versions of these tests may validate other
|
|
11
|
+
requirements and may change the test validation logic.
|
|
12
|
+
|
|
13
|
+
## Test Methodology
|
|
14
|
+
|
|
15
|
+
Inferno will simulate a DTR payer server and light EHR that will response to
|
|
16
|
+
requests for questionnaires and clinical data for the app under test to interact with.
|
|
17
|
+
The app will be expected to initiate requests to Inferno to elicit responses. Over the
|
|
18
|
+
course of these interactions, Inferno will seek to observe conformant handling of
|
|
19
|
+
DTR workflows and requirements around the retrieval, completion, and storage of
|
|
20
|
+
questionnaires.
|
|
21
|
+
|
|
22
|
+
Tests within this suite are associated with specific questionnaires that the app will
|
|
23
|
+
demonstrate completion of. In each case, the app under test will initiate a request to
|
|
24
|
+
the payer server simulated by Inferno for a questionnaire using the
|
|
25
|
+
`$questionnaire-package` operation. Inferno will always return the specific questionnaire
|
|
26
|
+
for the test being executed regardless of the input provided by the app, though it must
|
|
27
|
+
be conformant. The app will then be asked to complete the questionnaire, including
|
|
28
|
+
- Pre-populating answers based on directives in the questionnaire, which includes the
|
|
29
|
+
fetching of associated data from the light EHR simulated by Inferno
|
|
30
|
+
- Rendering the questionnaire for users and allowing them to make additional updates.
|
|
31
|
+
These tests can include specific directions on details to include in the completed
|
|
32
|
+
questionnaire.
|
|
33
|
+
- Storing the completed questionnaire back to the light EHR simulated by Inferno. Inferno
|
|
34
|
+
will validate the stored questionnaire, including pre-populated values (Inferno knows
|
|
35
|
+
the pre-population logic and the data used in calculation) and other conformance details.
|
|
36
|
+
|
|
37
|
+
Apps will be required to complete all questionnaires in the suite, which in aggregate
|
|
38
|
+
contain all questionnaire features that apps must support. Currently, the suite includes
|
|
39
|
+
two questionnaires:
|
|
40
|
+
1. A fictious "dinner" questionnaire created for these tests. It tests basic
|
|
41
|
+
item rendering and pre-population.
|
|
42
|
+
2. A Respiratory Assist Device questionnaire pulled from the DTR reference implementation.
|
|
43
|
+
It tests additional features and represents a more realistic questionnaire.
|
|
44
|
+
Additional questionnaires will be added in the future.
|
|
45
|
+
|
|
46
|
+
All requests sent by the app will be checked
|
|
47
|
+
for conformance to the DTR IG requirements individually and used in aggregate to determine
|
|
48
|
+
whether required features and functionality are present. HL7® FHIR® resources are
|
|
49
|
+
validated with the Java validator using `tx.fhir.org` as the terminology server.
|
|
50
|
+
|
|
51
|
+
## Running the Tests
|
|
52
|
+
|
|
53
|
+
### Quick Start
|
|
54
|
+
|
|
55
|
+
Inferno does not currently include the ability to launch the client. Therefore, clients
|
|
56
|
+
must be manually configured to point to Inferno's simulated server endpoints. The endpoints
|
|
57
|
+
can be inferred from the URL of the test session which will be of the form `[URL prefix]/dtr_smart_app/[session id]`: (NOTE: both currently use the same URL)
|
|
58
|
+
- Payer Server Base FHIR URL: `[URL prefix]/custom/dtr_smart_app/fhir`
|
|
59
|
+
- Light EHR Base FHIR URL: `[URL prefix]/custom/dtr_smart_app/fhir`
|
|
60
|
+
|
|
61
|
+
In order for Inferno to associate requests sent to locations under these base URLs with this session,
|
|
62
|
+
it needs to know the bearer token that the app will send on requests, for which
|
|
63
|
+
there are two options.
|
|
64
|
+
|
|
65
|
+
1. If you want to choose your own bearer token, then
|
|
66
|
+
1. Select the "2. Basic Workflows" test from the list on the left (or other target test).
|
|
67
|
+
2. Click the '*Run All Tests*' button on the right.
|
|
68
|
+
3. In the "access_token" field, enter the bearer token that will be sent by the client
|
|
69
|
+
under test (as part of the Authorization header - `Bearer <provided value>`).
|
|
70
|
+
4. Click the '*Submit*' button at the bottom of the dialog.
|
|
71
|
+
2. If you want to use a client_id to obtain an access token, then
|
|
72
|
+
1. Click the '*Run All Tests*' button on the right.
|
|
73
|
+
2. Provide the client's registered id "client_id" field of the input (NOTE, Inferno doesn't support the
|
|
74
|
+
registration API, so this must be obtained from another system or configured manually).
|
|
75
|
+
3. Click the '*Submit*' button at the bottom of the dialog.
|
|
76
|
+
4. Make a token request that includes the specified client id to the
|
|
77
|
+
`[URL prefix]/custom/dtr_smart_app/mock_auth/token` endpoint to get
|
|
78
|
+
an access token to use on the request of the requests.
|
|
79
|
+
|
|
80
|
+
In either case, the tests will continue from that point. Further executions of tests under
|
|
81
|
+
this session will also use the selected bearer token.
|
|
82
|
+
|
|
83
|
+
Note: authentication options for these tests have not been finalized and are subject to change.
|
|
84
|
+
|
|
85
|
+
### Postman-based Demo
|
|
86
|
+
|
|
87
|
+
If you do not have a DTR SMART app but would like to try the tests out, you can use
|
|
88
|
+
[this Postman collection](https://github.com/inferno-framework/davinci-dtr-test-kit/blob/main/config/DTR%20Test%20Kit.postman_collection.json)
|
|
89
|
+
to make requests against Inferno. This does not include the capability to render the complete the
|
|
90
|
+
questionnaires, but does have samples of correctly and incorrectly completed QuestionnaireResponses.
|
|
91
|
+
The following is a list of tests with the Postman requests that can be used with them:
|
|
92
|
+
|
|
93
|
+
- **2.1** *Static Questionnaire Workflow*: use requests in the `Static Dinner` folder
|
|
94
|
+
- **2.1.1.01** *Invoke the DTR Questionnaire Package operation*: submit request `Questionnaire Package for Dinner (Static)` while this test is waiting.
|
|
95
|
+
- **2.1.3.01** *Save the QuestionnaireResponse after completing it*: submit request `Save QuestionnaireResponse for Dinner (Static)` while this test is waiting. If you want to see a failure, submit request `Save QuestionnaireResponse for Dinner (Static) - missing origin extension` instead.
|
|
96
|
+
- **3.1** *Respiratory Assist Device Questionnaire Workflow*: use requests in the `Respiratory Assist Device` folder
|
|
97
|
+
- **3.1.1.01** *Invoke the DTR Questionnaire Package operation*: submit request `Questionnaire Package for Resp Assist Device` while this test is waiting.
|
|
98
|
+
- **3.1.3.01** *Save the QuestionnaireResponse after completing it*: submit request `Save Questionnaire Response for Resp Assist Device` while this test is waiting. If you want to see a failure, submit request `Save Questionnaire Response for Resp Assist Device - unexpected override` instead.
|
|
99
|
+
|
|
100
|
+
## Limitations
|
|
101
|
+
|
|
102
|
+
The DTR IG is a complex specification and these tests currently validate SMART app
|
|
103
|
+
configuration to only part of it. Future versions of the test suite will test further
|
|
104
|
+
features. A few specific features of interest are listed below.
|
|
105
|
+
|
|
106
|
+
### Launching and security
|
|
107
|
+
|
|
108
|
+
The primary limitation on this test suite is that it requires the client under test
|
|
109
|
+
to be manually configured to point to the Inferno endpoints and send a bearer token.
|
|
110
|
+
In the future, the tests will provide a mechanism for launching the application using
|
|
111
|
+
the SMART app launch mechanism. To provide feedback and input on the design of this feature,
|
|
112
|
+
submit a ticket [here](https://github.com/inferno-framework/davinci-pas-test-kit/issues).
|
|
113
|
+
|
|
114
|
+
### Questionnaire Feature Coverage
|
|
115
|
+
|
|
116
|
+
Not all questionnaire features that are must support within the DTR IG are currently represented
|
|
117
|
+
in questionnaires tested by the IG. Adaptive questionnaires are a notable omission.
|
|
118
|
+
Additional questionnaires testing additional features will be added in the future.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require_relative 'ext/inferno_core/runnable'
|
|
2
|
+
require_relative 'ext/inferno_core/record_response_route'
|
|
3
|
+
require_relative 'ext/inferno_core/request'
|
|
4
|
+
require_relative 'client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group'
|
|
5
|
+
require_relative 'auth_groups/oauth2_authentication_group'
|
|
6
|
+
require_relative 'mock_payer'
|
|
7
|
+
require_relative 'version'
|
|
8
|
+
|
|
9
|
+
module DaVinciDTRTestKit
|
|
10
|
+
class DTRFullEHRSuite < Inferno::TestSuite
|
|
11
|
+
extend MockPayer
|
|
12
|
+
|
|
13
|
+
id :dtr_full_ehr
|
|
14
|
+
title 'Da Vinci DTR Full EHR Test Suite'
|
|
15
|
+
description %(
|
|
16
|
+
# Da Vinci DTR Full EHR Test Suite
|
|
17
|
+
|
|
18
|
+
This suite validates that an EHR or other application can act
|
|
19
|
+
as a full DTR application requesting questionnaires from a
|
|
20
|
+
payer server and using local data to complete and store them.
|
|
21
|
+
Inferno will act as payer server returning questionnaires
|
|
22
|
+
in response to queries from the system under test and validating
|
|
23
|
+
that they can be completed as expected.
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
version VERSION
|
|
27
|
+
|
|
28
|
+
# All FHIR validation requsets will use this FHIR validator
|
|
29
|
+
validator do
|
|
30
|
+
url ENV.fetch('VALIDATOR_URL')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
allow_cors QUESTIONNAIRE_PACKAGE_PATH
|
|
34
|
+
|
|
35
|
+
record_response_route :post, TOKEN_PATH, 'dtr_auth', method(:token_response) do |request|
|
|
36
|
+
DTRFullEHRSuite.extract_client_id(request)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
record_response_route :post, '/fhir/Questionnaire/$questionnaire-package', QUESTIONNAIRE_PACKAGE_TAG,
|
|
40
|
+
method(:questionnaire_package_response) do |request|
|
|
41
|
+
DTRFullEHRSuite.extract_bearer_token(request)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
resume_test_route :get, RESUME_PASS_PATH do |request|
|
|
45
|
+
DTRFullEHRSuite.extract_token_from_query_params(request)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
|
|
49
|
+
DTRFullEHRSuite.extract_token_from_query_params(request)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
group from: :oauth2_authentication
|
|
53
|
+
group from: :dtr_full_ehr_questionnaire_workflow
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require_relative 'version'
|
|
2
|
+
|
|
3
|
+
module DaVinciDTRTestKit
|
|
4
|
+
class DTRLightEHRSuite < Inferno::TestSuite
|
|
5
|
+
id :dtr_light_ehr
|
|
6
|
+
title 'Da Vinci DTR Light EHR Test Suite'
|
|
7
|
+
description %(
|
|
8
|
+
# Da Vinci DTR Light EHR Test Suite
|
|
9
|
+
|
|
10
|
+
This suite validates that an EMR or other application
|
|
11
|
+
can act as a data source for a DTR SMART App. Inferno
|
|
12
|
+
will act as a DTR SMART App making requests for data
|
|
13
|
+
against the system under test and storing completed
|
|
14
|
+
questionnaire responses.
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
version VERSION
|
|
18
|
+
|
|
19
|
+
# These inputs will be available to all tests in this suite
|
|
20
|
+
input :url,
|
|
21
|
+
title: 'FHIR Server Base Url'
|
|
22
|
+
|
|
23
|
+
input :credentials,
|
|
24
|
+
title: 'OAuth Credentials',
|
|
25
|
+
type: :oauth_credentials,
|
|
26
|
+
optional: true
|
|
27
|
+
|
|
28
|
+
# All FHIR requests in this suite will use this FHIR client
|
|
29
|
+
fhir_client do
|
|
30
|
+
url :url
|
|
31
|
+
oauth_credentials :credentials
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# All FHIR validation requsets will use this FHIR validator
|
|
35
|
+
validator do
|
|
36
|
+
url ENV.fetch('VALIDATOR_URL')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require_relative 'ext/inferno_core/runnable'
|
|
2
|
+
require_relative 'ext/inferno_core/record_response_route'
|
|
3
|
+
require_relative 'ext/inferno_core/request'
|
|
4
|
+
require_relative 'payer_server_groups/payer_server_static_group'
|
|
5
|
+
require_relative 'payer_server_groups/payer_server_adaptive_group'
|
|
6
|
+
require_relative 'tags'
|
|
7
|
+
require_relative 'mock_payer'
|
|
8
|
+
require_relative 'version'
|
|
9
|
+
|
|
10
|
+
module DaVinciDTRTestKit
|
|
11
|
+
class DTRPayerServerSuite < Inferno::TestSuite
|
|
12
|
+
extend MockPayer
|
|
13
|
+
|
|
14
|
+
id :dtr_payer_server
|
|
15
|
+
title 'Da Vinci DTR Payer Server Test Suite'
|
|
16
|
+
description File.read(File.join(__dir__, 'docs', 'dtr_payer_server_suite_description_v201.md'))
|
|
17
|
+
|
|
18
|
+
version VERSION
|
|
19
|
+
# These inputs will be available to all tests in this suite
|
|
20
|
+
|
|
21
|
+
input :retrieval_method,
|
|
22
|
+
title: 'Questionnaire Retrieval Method',
|
|
23
|
+
type: 'radio',
|
|
24
|
+
default: 'Both',
|
|
25
|
+
options: {
|
|
26
|
+
list_options: [
|
|
27
|
+
{
|
|
28
|
+
label: 'Static',
|
|
29
|
+
value: 'Static'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Adaptive',
|
|
33
|
+
value: 'Adaptive'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'Both',
|
|
37
|
+
value: 'Both'
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
input :url,
|
|
43
|
+
title: 'FHIR Server Base Url',
|
|
44
|
+
description: 'Required for All Flows'
|
|
45
|
+
|
|
46
|
+
input :access_token,
|
|
47
|
+
optional: true,
|
|
48
|
+
title: 'Access Token',
|
|
49
|
+
description: 'DTR Client Flow'
|
|
50
|
+
|
|
51
|
+
input :custom_endpoint,
|
|
52
|
+
optional: true,
|
|
53
|
+
title: 'Custom Endpoint for Accessing a Particular Resource',
|
|
54
|
+
description: 'Either Flow (optional)'
|
|
55
|
+
|
|
56
|
+
input :credentials,
|
|
57
|
+
title: 'OAuth Credentials',
|
|
58
|
+
type: :oauth_credentials,
|
|
59
|
+
optional: true
|
|
60
|
+
|
|
61
|
+
input_order :retrieval_method,
|
|
62
|
+
:url,
|
|
63
|
+
:access_token,
|
|
64
|
+
:custom_endpoint,
|
|
65
|
+
:initial_static_questionnaire_request,
|
|
66
|
+
:initial_adaptive_questionnaire_request,
|
|
67
|
+
:next_question_requests,
|
|
68
|
+
:credentials
|
|
69
|
+
|
|
70
|
+
# All FHIR requests in this suite will use this FHIR client
|
|
71
|
+
fhir_client do
|
|
72
|
+
url :url
|
|
73
|
+
oauth_credentials :credentials
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# All FHIR validation requsets will use this FHIR validator
|
|
77
|
+
validator do
|
|
78
|
+
url ENV.fetch('VALIDATOR_URL')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
allow_cors QUESTIONNAIRE_PACKAGE_PATH, NEXT_PATH
|
|
82
|
+
|
|
83
|
+
record_response_route :post, TOKEN_PATH, 'dtr_auth', method(:token_response) do |request|
|
|
84
|
+
DTRPayerServerSuite.extract_client_id(request)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
record_response_route :post, '/fhir/Questionnaire/$questionnaire-package', QUESTIONNAIRE_TAG,
|
|
88
|
+
method(:payer_questionnaire_response), resumes: method(:test_resumes?) do |request|
|
|
89
|
+
DTRPayerServerSuite.extract_bearer_token(request)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
record_response_route :post, '/fhir/Questionnaire/$next-question', NEXT_TAG,
|
|
93
|
+
method(:questionnaire_next_response), resumes: method(:test_resumes?) do |request|
|
|
94
|
+
DTRPayerServerSuite.extract_bearer_token(request)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
resume_test_route :get, RESUME_PASS_PATH do |request|
|
|
98
|
+
DTRPayerServerSuite.extract_token_from_query_params(request)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
group from: :payer_server_static_package
|
|
102
|
+
group from: :payer_server_adaptive_questionnaire
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
require_relative 'fixtures'
|
|
2
|
+
|
|
3
|
+
module DaVinciDTRTestKit
|
|
4
|
+
module DTRQuestionnaireResponseValidation
|
|
5
|
+
include Fixtures
|
|
6
|
+
|
|
7
|
+
CQL_EXPRESSION_EXTENSIONS = [
|
|
8
|
+
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression',
|
|
9
|
+
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression',
|
|
10
|
+
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression'
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def validate_questionnaire_pre_population(questionnaire_response, test_id)
|
|
14
|
+
# Requirements:
|
|
15
|
+
# - Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client
|
|
16
|
+
# SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and
|
|
17
|
+
# calculatedExpression extensions found in the Questionnaire for any enabled elements.
|
|
18
|
+
# - All items that are pre-populated (whether by the payer in the initial QuestionnaireResponse provided in the
|
|
19
|
+
# questionnaire package, or from data retrieved from the EHR) SHALL have their origin.source set to ‘auto’.
|
|
20
|
+
#
|
|
21
|
+
# Note that in the questionnaire fixture, all cql expression elements are enabled, so we don't filter
|
|
22
|
+
template_questionnaire_response = find_questionnaire_response_for_test_id(test_id)
|
|
23
|
+
raise "missing QuestionnaireResponse template for test #{test_id}" unless template_questionnaire_response.present?
|
|
24
|
+
|
|
25
|
+
questionnaire = find_questionnaire_instance_for_test_id(test_id)
|
|
26
|
+
raise "missing Questionnaire for test #{test_id}" unless questionnaire.present?
|
|
27
|
+
|
|
28
|
+
questionnaire_cql_expression_link_ids = collect_questionnaire_cql_expression_link_ids(questionnaire.item)
|
|
29
|
+
template_prepopulation_expectations = {}
|
|
30
|
+
template_override_expectations = {}
|
|
31
|
+
extract_expected_answers_from_template(template_questionnaire_response,
|
|
32
|
+
questionnaire_cql_expression_link_ids,
|
|
33
|
+
template_prepopulation_expectations,
|
|
34
|
+
template_override_expectations)
|
|
35
|
+
validation_errors = []
|
|
36
|
+
validate_cql_executed(questionnaire_response.item, questionnaire_cql_expression_link_ids,
|
|
37
|
+
template_prepopulation_expectations, template_override_expectations, validation_errors)
|
|
38
|
+
|
|
39
|
+
validation_errors.each { |msg| messages << { type: 'error', message: msg } }
|
|
40
|
+
assert validation_errors.blank?, 'QuestionnaireResponse is not conformant. Check messages for issues found.'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validate_cql_executed(actual_items, questionnaire_cql_expression_link_ids, template_prepopulation_expectations,
|
|
44
|
+
template_override_expectations, error_messages)
|
|
45
|
+
|
|
46
|
+
actual_items&.each do |item_to_validate|
|
|
47
|
+
link_id = item_to_validate.linkId
|
|
48
|
+
if questionnaire_cql_expression_link_ids.include?(link_id)
|
|
49
|
+
if template_prepopulation_expectations.key?(link_id)
|
|
50
|
+
check_item_prepopulation(item_to_validate, template_prepopulation_expectations[link_id], error_messages,
|
|
51
|
+
false)
|
|
52
|
+
elsif template_override_expectations.include?(link_id)
|
|
53
|
+
check_item_prepopulation(item_to_validate, template_override_expectations[link_id], error_messages, true)
|
|
54
|
+
else
|
|
55
|
+
raise "template missing expectation for question `#{link_id}`"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
validate_cql_executed(item_to_validate.item, questionnaire_cql_expression_link_ids,
|
|
60
|
+
template_prepopulation_expectations, template_override_expectations, error_messages)
|
|
61
|
+
end
|
|
62
|
+
error_messages
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def extract_expected_answers_from_template(template_questionnaire_response,
|
|
66
|
+
questionnaire_cql_expression_link_ids,
|
|
67
|
+
expected_prepopulated = {},
|
|
68
|
+
expected_overrides = {})
|
|
69
|
+
|
|
70
|
+
questionnaire_cql_expression_link_ids.each do |target_link_id|
|
|
71
|
+
target_item = find_item_by_link_id(template_questionnaire_response.item, target_link_id)
|
|
72
|
+
raise "Template QuestionnaireResponse missing item with link id `#{target_link_id}`" unless target_item.present?
|
|
73
|
+
|
|
74
|
+
target_item_answer = target_item.answer.first
|
|
75
|
+
unless target_item_answer.present?
|
|
76
|
+
raise "Template QuestionnaireResponse missing an answer for item with link id `#{target_link_id}`"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
origin_extension = find_extension(target_item_answer,
|
|
80
|
+
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin')
|
|
81
|
+
source_extension = find_extension(origin_extension, 'source')
|
|
82
|
+
|
|
83
|
+
unless source_extension.present?
|
|
84
|
+
raise "Template QuestionnaireResponse item `#{target_link_id}` missing the `origin.source` extension"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# TODO: handle other data types
|
|
88
|
+
if source_extension.value == 'auto'
|
|
89
|
+
expected_prepopulated[target_link_id] = target_item_answer.value
|
|
90
|
+
elsif source_extension.value == 'override'
|
|
91
|
+
expected_overrides[target_link_id] = target_item_answer.value
|
|
92
|
+
else
|
|
93
|
+
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{source_extension.value}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate_data_requirements_retrieved(expected_questionnaire_response, questionnaire_response)
|
|
99
|
+
error_messages = []
|
|
100
|
+
|
|
101
|
+
DATA_REQUIREMENT_ANSWERS.each do |library_name, link_id|
|
|
102
|
+
expected = find_item_by_link_id(expected_questionnaire_response.item, link_id).answer.first.value
|
|
103
|
+
actual = find_item_by_link_id(questionnaire_response.item, link_id)&.answer&.first&.value
|
|
104
|
+
next if coding_equal?(expected, actual)
|
|
105
|
+
|
|
106
|
+
error_messages << "dataRequirement not satisfied for Library '#{library_name}'. Expected answer to " \
|
|
107
|
+
"question with linkId `#{link_id}` to have coding with system: '`#{expected.system}`' " \
|
|
108
|
+
"and value: '`#{expected.code}`'"
|
|
109
|
+
end
|
|
110
|
+
error_messages
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def check_item_prepopulation(item, expected_answer, error_list, override)
|
|
114
|
+
answer = item.answer.first
|
|
115
|
+
if answer&.value&.present?
|
|
116
|
+
# check answer
|
|
117
|
+
if override && answer.value == expected_answer
|
|
118
|
+
error_list << "Answer to item `#{item.linkId}` was not overriden from the pre-populated value. " \
|
|
119
|
+
"Found #{expected_answer}, but should be different"
|
|
120
|
+
elsif !override && answer.value != expected_answer
|
|
121
|
+
error_list << "answer to item `#{item.linkId}` contains unexpected value. Expected: #{expected_answer}. " \
|
|
122
|
+
"Found #{answer.value}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# check origin.source extension
|
|
126
|
+
origin_extension = find_extension(answer,
|
|
127
|
+
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin')
|
|
128
|
+
source_extension = find_extension(origin_extension, 'source')
|
|
129
|
+
|
|
130
|
+
if source_extension.present?
|
|
131
|
+
expected_source_value = override ? 'override' : 'auto'
|
|
132
|
+
if source_extension.value != expected_source_value
|
|
133
|
+
error_list << "`origin.source` extension on item `#{item.linkId}` contains unexpected value. Expected: " \
|
|
134
|
+
"#{expected_source_value}. Found #{source_extension.value}"
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
error_list << "Required `origin.source` extension not present on answer to item `#{item.linkId}`"
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
error_list << "No answer for item `#{item.linkId}`"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def find_item_by_link_id(items, link_id)
|
|
145
|
+
items.each do |item|
|
|
146
|
+
return item if item.linkId == link_id
|
|
147
|
+
|
|
148
|
+
match = find_item_by_link_id(item.item, link_id)
|
|
149
|
+
return match if match
|
|
150
|
+
end
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def find_extension(element, url)
|
|
155
|
+
element&.extension&.find { |e| e.url == url }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def collect_questionnaire_cql_expression_link_ids(items, link_ids = [])
|
|
159
|
+
items.each do |item|
|
|
160
|
+
link_ids << item.linkId if item_is_cql_expression?(item)
|
|
161
|
+
collect_questionnaire_cql_expression_link_ids(item.item, link_ids) if item&.item&.any?
|
|
162
|
+
end
|
|
163
|
+
link_ids
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def item_is_cql_expression?(item)
|
|
167
|
+
item.extension&.any? { |ext| CQL_EXPRESSION_EXTENSIONS.include?(ext.url) }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def answer_value_equal?(expected, actual)
|
|
171
|
+
return coding_equal?(expected.value, actual.value) if expected.valueCoding.present?
|
|
172
|
+
|
|
173
|
+
expected.value == actual.value
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def coding_equal?(expected, actual)
|
|
177
|
+
expected.system == actual&.system && expected.code == actual&.code
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|