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.
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