davinci_dtr_test_kit 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|