davinci_dtr_test_kit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_dtr_test_kit/auth_groups/oauth2_authentication_group.rb +51 -0
  4. data/lib/davinci_dtr_test_kit/auth_groups/token_request_test.rb +25 -0
  5. data/lib/davinci_dtr_test_kit/auth_groups/token_validation_test.rb +13 -0
  6. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group.rb +20 -0
  7. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_questionnaire_response_save_test.rb +31 -0
  8. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +89 -0
  9. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_attestation_test.rb +29 -0
  10. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_override_attestation_test.rb +30 -0
  11. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/rendering_enabled_questions_attestation_test.rb +30 -0
  12. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +19 -0
  13. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_package_group.rb +16 -0
  14. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +32 -0
  15. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_group.rb +14 -0
  16. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_group.rb +23 -0
  17. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_save_test.rb +31 -0
  18. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_smart_app_questionnaire_workflow_group.rb +22 -0
  19. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_test.rb +36 -0
  20. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_validation_test.rb +35 -0
  21. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +28 -0
  22. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +30 -0
  23. data/lib/davinci_dtr_test_kit/cql_test.rb +387 -0
  24. data/lib/davinci_dtr_test_kit/docs/dtr_payer_server_suite_description_v201.md +127 -0
  25. data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +118 -0
  26. data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +55 -0
  27. data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +39 -0
  28. data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +104 -0
  29. data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +180 -0
  30. data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +85 -0
  31. data/lib/davinci_dtr_test_kit/ext/inferno_core/record_response_route.rb +98 -0
  32. data/lib/davinci_dtr_test_kit/ext/inferno_core/request.rb +19 -0
  33. data/lib/davinci_dtr_test_kit/ext/inferno_core/runnable.rb +35 -0
  34. data/lib/davinci_dtr_test_kit/fixture_loader.rb +99 -0
  35. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_burrito.json +170 -0
  36. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_hamburger.json +175 -0
  37. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_initial.json +140 -0
  38. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json +95 -0
  39. data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_dinner_order_static.json +283 -0
  40. data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_response_dinner_order_static.json +170 -0
  41. data/lib/davinci_dtr_test_kit/fixtures/pre_populated_questionnaire_response.json +581 -0
  42. data/lib/davinci_dtr_test_kit/fixtures/questionnaire_package.json +2121 -0
  43. data/lib/davinci_dtr_test_kit/fixtures.rb +65 -0
  44. data/lib/davinci_dtr_test_kit/mock_ehr.rb +72 -0
  45. data/lib/davinci_dtr_test_kit/mock_payer.rb +142 -0
  46. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +19 -0
  47. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +20 -0
  48. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +19 -0
  49. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +20 -0
  50. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +19 -0
  51. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +88 -0
  52. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_test.rb +41 -0
  53. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +44 -0
  54. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +40 -0
  55. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +42 -0
  56. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +49 -0
  57. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +61 -0
  58. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +17 -0
  59. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +43 -0
  60. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_static_group.rb +51 -0
  61. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +19 -0
  62. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +20 -0
  63. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +19 -0
  64. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_test.rb +33 -0
  65. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +46 -0
  66. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +50 -0
  67. data/lib/davinci_dtr_test_kit/tags.rb +8 -0
  68. data/lib/davinci_dtr_test_kit/urls.rb +50 -0
  69. data/lib/davinci_dtr_test_kit/validation_test.rb +72 -0
  70. data/lib/davinci_dtr_test_kit/version.rb +5 -0
  71. data/lib/davinci_dtr_test_kit.rb +4 -0
  72. 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