davinci_crd_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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_crd_test_kit/card_responses/companions_prerequisites.json +58 -0
  4. data/lib/davinci_crd_test_kit/card_responses/create_update_coverage_information.json +20 -0
  5. data/lib/davinci_crd_test_kit/card_responses/external_reference.json +21 -0
  6. data/lib/davinci_crd_test_kit/card_responses/instructions.json +14 -0
  7. data/lib/davinci_crd_test_kit/card_responses/launch_smart_app.json +21 -0
  8. data/lib/davinci_crd_test_kit/card_responses/propose_alternate_request.json +71 -0
  9. data/lib/davinci_crd_test_kit/card_responses/request_form_completion.json +227 -0
  10. data/lib/davinci_crd_test_kit/cards_validation.rb +234 -0
  11. data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +762 -0
  12. data/lib/davinci_crd_test_kit/client_hook_request_validation.rb +15 -0
  13. data/lib/davinci_crd_test_kit/client_hooks_group.rb +706 -0
  14. data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +71 -0
  15. data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +48 -0
  16. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +40 -0
  17. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +39 -0
  18. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +232 -0
  19. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +40 -0
  20. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +60 -0
  21. data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +40 -0
  22. data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +68 -0
  23. data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +68 -0
  24. data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +41 -0
  25. data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +40 -0
  26. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +63 -0
  27. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +151 -0
  28. data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +79 -0
  29. data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +76 -0
  30. data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +79 -0
  31. data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +65 -0
  32. data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +34 -0
  33. data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +61 -0
  34. data/lib/davinci_crd_test_kit/crd_client_suite.rb +156 -0
  35. data/lib/davinci_crd_test_kit/crd_jwks.json +59 -0
  36. data/lib/davinci_crd_test_kit/crd_options.rb +9 -0
  37. data/lib/davinci_crd_test_kit/crd_server_suite.rb +115 -0
  38. data/lib/davinci_crd_test_kit/ext/inferno_core/runnable.rb +22 -0
  39. data/lib/davinci_crd_test_kit/hook_request_field_validation.rb +410 -0
  40. data/lib/davinci_crd_test_kit/jwks.rb +25 -0
  41. data/lib/davinci_crd_test_kit/jwt_helper.rb +74 -0
  42. data/lib/davinci_crd_test_kit/mock_service_response.rb +421 -0
  43. data/lib/davinci_crd_test_kit/routes/cds-services.json +74 -0
  44. data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +18 -0
  45. data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +93 -0
  46. data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +15 -0
  47. data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +173 -0
  48. data/lib/davinci_crd_test_kit/server_discovery_group.rb +59 -0
  49. data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +144 -0
  50. data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +144 -0
  51. data/lib/davinci_crd_test_kit/server_hook_request_validation.rb +15 -0
  52. data/lib/davinci_crd_test_kit/server_hooks_group.rb +69 -0
  53. data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +173 -0
  54. data/lib/davinci_crd_test_kit/server_order_select_group.rb +169 -0
  55. data/lib/davinci_crd_test_kit/server_order_sign_group.rb +198 -0
  56. data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +23 -0
  57. data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +70 -0
  58. data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +47 -0
  59. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +32 -0
  60. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +58 -0
  61. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +121 -0
  62. data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +72 -0
  63. data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +88 -0
  64. data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +65 -0
  65. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +28 -0
  66. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +36 -0
  67. data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +79 -0
  68. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +25 -0
  69. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +28 -0
  70. data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +38 -0
  71. data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +65 -0
  72. data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +86 -0
  73. data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +30 -0
  74. data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +41 -0
  75. data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +43 -0
  76. data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +82 -0
  77. data/lib/davinci_crd_test_kit/suggestion_actions_validation.rb +123 -0
  78. data/lib/davinci_crd_test_kit/tags.rb +8 -0
  79. data/lib/davinci_crd_test_kit/test_helper.rb +23 -0
  80. data/lib/davinci_crd_test_kit/urls.rb +52 -0
  81. data/lib/davinci_crd_test_kit/version.rb +3 -0
  82. data/lib/davinci_crd_test_kit.rb +2 -0
  83. metadata +170 -0
@@ -0,0 +1,34 @@
1
+ module DaVinciCRDTestKit
2
+ class TokenHeaderTest < Inferno::Test
3
+ id :crd_token_header
4
+ title 'Authorization token header contains required information'
5
+ description %(
6
+ Verify that the JWT header contains the header fields required by the [CDS hooks spec](https://cds-hooks.hl7.org/2.0#trusting-cds-clients).
7
+ The `alg`, `kid`, and `typ` fields are required. This test also verifies that the `typ` field is set to `JWT` and
8
+ that the key used to sign the token can be identified in the JWKS.
9
+ )
10
+
11
+ input :auth_token_header_json, :crd_jwks_keys_json
12
+ output :auth_token_jwk_json
13
+
14
+ run do
15
+ header = JSON.parse(auth_token_header_json)
16
+
17
+ algorithm = header['alg']
18
+ assert algorithm.present?, 'Token header must have the `alg` field'
19
+ assert algorithm != 'none', 'Token header `alg` field cannot be set to none'
20
+
21
+ assert header['typ'].present?, 'Token header must have the `typ` field'
22
+ assert header['typ'] == 'JWT', "Token header `typ` field must be set to 'JWT', instead was #{header['typ']}"
23
+
24
+ assert header['kid'].present?, 'Token header must have the `kid` field'
25
+ kid = header['kid']
26
+ keys = JSON.parse(crd_jwks_keys_json)
27
+
28
+ jwk = keys.find { |key| key['kid'] == kid }
29
+ assert jwk.present?, "JWKS did not contain a public key with an id of `#{kid}`"
30
+
31
+ output auth_token_jwk_json: jwk.to_json
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,61 @@
1
+ module DaVinciCRDTestKit
2
+ class TokenPayloadTest < Inferno::Test
3
+ include URLs
4
+ id :crd_token_payload
5
+ title 'Authorization token payload has required claims and a valid signature'
6
+ description %(
7
+ Verify that the JWT payload contains the payload fields required by the
8
+ [CDS hooks spec](https://cds-hooks.hl7.org/2.0#trusting-cds-clients).
9
+ The `iss`, `aud`, `exp`, `iat`, and `jti` claims are required.
10
+ Additionally:
11
+
12
+ - `iss` must match the `issuer` from the `iss` input
13
+ - `aud` must match the URL of the CDS Service endpoint being invoked
14
+ - `exp` must represent a time in the future
15
+ - `jti` must be a non-blank string that uniquely identifies this authentication JWT
16
+ )
17
+
18
+ REQUIRED_CLAIMS = ['iss', 'aud', 'exp', 'iat', 'jti'].freeze
19
+
20
+ def required_claims
21
+ REQUIRED_CLAIMS.dup
22
+ end
23
+
24
+ def hook_url
25
+ base_url + config.options[:hook_path]
26
+ end
27
+
28
+ input :auth_token,
29
+ :auth_token_jwk_json,
30
+ :iss
31
+
32
+ run do
33
+ begin
34
+ jwk = JSON.parse(auth_token_jwk_json).deep_symbolize_keys
35
+
36
+ payload, =
37
+ JWT.decode(
38
+ auth_token,
39
+ JWT::JWK.import(jwk).public_key,
40
+ true,
41
+ algorithms: [jwk[:alg]],
42
+ exp_leeway: 60,
43
+ iss:,
44
+ aud: hook_url,
45
+ verify_not_before: false,
46
+ verify_iat: false,
47
+ verify_jti: true,
48
+ verify_iss: true,
49
+ verify_aud: true
50
+ )
51
+ rescue StandardError => e
52
+ assert false, "Token validation error: #{e.message}"
53
+ end
54
+
55
+ missing_claims = required_claims - payload.keys
56
+ missing_claims_string = missing_claims.map { |claim| "`#{claim}`" }.join(', ')
57
+
58
+ assert missing_claims.empty?, "JWT payload missing required claims: #{missing_claims_string}"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,156 @@
1
+ require_relative 'client_fhir_api_group'
2
+ require_relative 'client_hooks_group'
3
+ require_relative 'routes/cds_services_discovery_handler'
4
+ require_relative 'tags'
5
+ require_relative 'urls'
6
+ require_relative 'crd_options'
7
+ require_relative 'routes/hook_request_endpoint'
8
+ require_relative 'ext/inferno_core/runnable'
9
+ require_relative 'version'
10
+
11
+ module DaVinciCRDTestKit
12
+ class CRDClientSuite < Inferno::TestSuite
13
+ id :crd_client
14
+ title 'Da Vinci CRD Client Test Suite'
15
+ description <<~DESCRIPTION
16
+ The Da Vinci CRD Client Test Suite tests the conformance of client systems
17
+ to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
18
+ Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
19
+
20
+ ## Overview
21
+ This suite contains two groups of tests. The Hooks group receives and
22
+ responds to incoming CDS Hooks requests from CRD clients. The FHIR API
23
+ group makes FHIR requests to CRD Clients to verify that they support the
24
+ FHIR interactions defined in the implementation guide.
25
+
26
+ ## CDS Services
27
+ This suite provides basic CDS services for [the six hooks contained in the
28
+ implementation
29
+ guide](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html). The discovery
30
+ endpoint is located at:
31
+
32
+ * `#{Inferno::Application['base_url']}/custom/#{id}/cds-services`
33
+
34
+ ## SMART App Launch
35
+ Use this information when registering Inferno as a SMART App:
36
+
37
+ * Launch URI: `#{SMARTAppLaunch::AppLaunchTest.config.options[:launch_uri]}`
38
+ * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
39
+
40
+ If a client receives a SMART App Launch card in a response and would like
41
+ to test their ability to launch Inferno as a SMART App, first run the
42
+ SMART on FHIR Discovery and SMART EHR Launch groups under FHIR API >
43
+ Authorization. When running the SMART EHR Launch group, Inferno will wait
44
+ for the incoming SMART App Launch request, and this is the time to perform
45
+ the launch from the client being tested.
46
+
47
+ ## Running the Tests
48
+ If you would like to try out the tests against [the public CRD reference
49
+ client](https://crd-request-generator.davinci.hl7.org/), you can do so by:
50
+ 1. Selecting the *CRD Request Generator RI* option from the Preset
51
+ dropdown in the upper left.
52
+ 2. Selecting the *order-sign* hook group on the left menu.
53
+ 3. Clicking on the *RUN TESTS* button in the upper right.
54
+ 4. Clicking the *Submit* button at the bottom of the input dialog.
55
+ 5. Follow the instructions in the wait dialog.
56
+ 6. Open the reference client in another tab/browser.
57
+ 7. Update the *CRD Server* field in the client configuration to point to
58
+ the discovery endpoint of this suite provided above, and the *Order
59
+ Sign Rest End Point*
60
+ to the service id provided in the wait dialog.
61
+ 8. Select the patient data to be used to form the request, then submit the
62
+ request.
63
+
64
+ You can run these tests using your own client by updating the inputs with
65
+ your own data.
66
+
67
+ Note that:
68
+ - You can only sequentially *RUN ALL TESTS* if your system supports all
69
+ hooks.
70
+ - Systems are not expected to pass the *FHIR RESTful Capabilities* tests
71
+ based on the provided inputs, as the resource might not exist on the
72
+ client's FHIR server.
73
+
74
+ ## Limitations
75
+ The test suite does not implement any sort of payer business logic, so the
76
+ responses to hook calls are simple hard-coded responses. Hook
77
+ configuration is not tested.
78
+ DESCRIPTION
79
+
80
+ suite_summary <<~SUMMARY
81
+ The Da Vinci CRD Client Test Suite tests the conformance of client systems
82
+ to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
83
+ Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
84
+ SUMMARY
85
+
86
+ version VERSION
87
+
88
+ links [
89
+ {
90
+ label: 'Report Issue',
91
+ url: 'https://github.com/inferno-framework/davinci-crd-test-kit/issues'
92
+ },
93
+ {
94
+ label: 'Open Source',
95
+ url: 'https://github.com/inferno-framework/davinci-crd-test-kit'
96
+ },
97
+ {
98
+ label: 'Download',
99
+ url: 'https://github.com/inferno-framework/davinci-crd-test-kit/releases'
100
+ }
101
+ ]
102
+
103
+ fhir_resource_validator do
104
+ igs('hl7.fhir.us.davinci-crd', 'hl7.fhir.us.core')
105
+
106
+ exclude_message do |message|
107
+ message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
108
+ end
109
+ end
110
+
111
+ suite_option :smart_app_launch_version,
112
+ title: 'SMART App Launch Version',
113
+ list_options: [
114
+ {
115
+ label: 'SMART App Launch 1.0.0',
116
+ value: CRDOptions::SMART_1
117
+ },
118
+ {
119
+ label: 'SMART App Launch 2.0.0',
120
+ value: CRDOptions::SMART_2
121
+ }
122
+ ]
123
+
124
+ def self.test_resumes?(test)
125
+ !test.config.options[:accepts_multiple_requests]
126
+ end
127
+
128
+ def self.extract_token_from_query_params(request)
129
+ request.query_parameters['token']
130
+ end
131
+
132
+ route :get, '/cds-services', Routes::CDSServicesDiscoveryHandler
133
+ # TODO
134
+ # route :post, '/cds-services/:cds-service_id', cds_service_handler
135
+
136
+ allow_cors APPOINTMENT_BOOK_PATH, ENCOUNTER_START_PATH, ENCOUNTER_DISCHARGE_PATH, ORDER_DISPATCH_PATH,
137
+ ORDER_SELECT_PATH, ORDER_SIGN_PATH
138
+ suite_endpoint :post, APPOINTMENT_BOOK_PATH, HookRequestEndpoint
139
+ suite_endpoint :post, ENCOUNTER_START_PATH, HookRequestEndpoint
140
+ suite_endpoint :post, ENCOUNTER_DISCHARGE_PATH, HookRequestEndpoint
141
+ suite_endpoint :post, ORDER_DISPATCH_PATH, HookRequestEndpoint
142
+ suite_endpoint :post, ORDER_SELECT_PATH, HookRequestEndpoint
143
+ suite_endpoint :post, ORDER_SIGN_PATH, HookRequestEndpoint
144
+
145
+ resume_test_route :get, RESUME_PASS_PATH do |request|
146
+ CRDClientSuite.extract_token_from_query_params(request)
147
+ end
148
+ resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
149
+ CRDClientSuite.extract_token_from_query_params(request)
150
+ end
151
+
152
+ group from: :crd_client_hooks
153
+
154
+ group from: :crd_client_fhir_api
155
+ end
156
+ end
@@ -0,0 +1,59 @@
1
+ {
2
+ "keys": [
3
+ {
4
+ "kty": "EC",
5
+ "crv": "P-384",
6
+ "x": "JQKTsV6PT5Szf4QtDA1qrs0EJ1pbimQmM2SKvzOlIAqlph3h1OHmZ2i7MXahIF2C",
7
+ "y": "bRWWQRJBgDa6CTgwofYrHjVGcO-A7WNEnu4oJA5OUJPPPpczgx1g2NsfinK-D2Rw",
8
+ "use": "sig",
9
+ "key_ops": [
10
+ "verify"
11
+ ],
12
+ "ext": true,
13
+ "kid": "4b49a739d1eb115b3225f4cf9beb6d1b",
14
+ "alg": "ES384"
15
+ },
16
+ {
17
+ "kty": "EC",
18
+ "crv": "P-384",
19
+ "d": "kDkn55p7gryKk2tj6z2ij7ExUnhi0ngxXosvqa73y7epwgthFqaJwApmiXXU2yhK",
20
+ "x": "JQKTsV6PT5Szf4QtDA1qrs0EJ1pbimQmM2SKvzOlIAqlph3h1OHmZ2i7MXahIF2C",
21
+ "y": "bRWWQRJBgDa6CTgwofYrHjVGcO-A7WNEnu4oJA5OUJPPPpczgx1g2NsfinK-D2Rw",
22
+ "key_ops": [
23
+ "sign"
24
+ ],
25
+ "ext": true,
26
+ "kid": "4b49a739d1eb115b3225f4cf9beb6d1b",
27
+ "alg": "ES384"
28
+ },
29
+ {
30
+ "kty": "RSA",
31
+ "alg": "RS384",
32
+ "n": "vjbIzTqiY8K8zApeNng5ekNNIxJfXAue9BjoMrZ9Qy9m7yIA-tf6muEupEXWhq70tC7vIGLqJJ4O8m7yiH8H2qklX2mCAMg3xG3nbykY2X7JXtW9P8VIdG0sAMt5aZQnUGCgSS3n0qaooGn2LUlTGIR88Qi-4Nrao9_3Ki3UCiICeCiAE224jGCg0OlQU6qj2gEB3o-DWJFlG_dz1y-Mxo5ivaeM0vWuodjDrp-aiabJcSF_dx26sdC9dZdBKXFDq0t19I9S9AyGpGDJwzGRtWHY6LsskNHLvo8Zb5AsJ9eRZKpnh30SYBZI9WHtzU85M9WQqdScR69Vyp-6Uhfbvw",
33
+ "e": "AQAB",
34
+ "use": "sig",
35
+ "key_ops": [
36
+ "verify"
37
+ ],
38
+ "ext": true,
39
+ "kid": "b41528b6f37a9500edb8a905a595bdd7"
40
+ },
41
+ {
42
+ "kty": "RSA",
43
+ "alg": "RS384",
44
+ "n": "vjbIzTqiY8K8zApeNng5ekNNIxJfXAue9BjoMrZ9Qy9m7yIA-tf6muEupEXWhq70tC7vIGLqJJ4O8m7yiH8H2qklX2mCAMg3xG3nbykY2X7JXtW9P8VIdG0sAMt5aZQnUGCgSS3n0qaooGn2LUlTGIR88Qi-4Nrao9_3Ki3UCiICeCiAE224jGCg0OlQU6qj2gEB3o-DWJFlG_dz1y-Mxo5ivaeM0vWuodjDrp-aiabJcSF_dx26sdC9dZdBKXFDq0t19I9S9AyGpGDJwzGRtWHY6LsskNHLvo8Zb5AsJ9eRZKpnh30SYBZI9WHtzU85M9WQqdScR69Vyp-6Uhfbvw",
45
+ "e": "AQAB",
46
+ "d": "rriV9GYimi5by7TOW4xNh6_gYBHVRDBsft2OFF8qapdVHt2GNuRDDxc_B6ga6TY2Enh2MLKLTr1dD3W4FIdTCJiMerrorp07FJS7nJEMgWQDxrfgkX4_EqrhW42L5d4vypYnRXEEW6u4gzkx5uFOkdvJBIK7CsIdSaBFYhochnynNgvbKWasi4rl2hayEH8tdf3B7Z6OIH9alspBTaq3j_zJt_KkrpYEzIUb4UgALB5NTWn5YKr0Avk_asOg8YfjViQwO9ASGaWjQeJ2Rx8OEQwBMQHSDMCSWNiWmYOu9PcwSZFc1vLxqzyIM8QrQSJHCCMo_wGYgke_r0CLeONHEQ",
47
+ "p": "5hH_QApWGeobRi1n7XbMfJYohB8K3JDPa0MspfplHpJ-17JiGG2sNoBdBcpaPRf9OX48P8VqO0qrSSRAk-I-uO6OO9BHbIukXJILqnY2JmurYzbcYbt5FVbknlHRJojkF6-7sFBazpueUlOnXCw7X7Z_SkfNE4QX5Ejm2Zm5mek",
48
+ "q": "06bZz7c7K9s1-aEZsxYnLJ9eTpKlt1tIBDA_LwIh5W3w259pes2kUtimbnkyOf-V2ZIERsFCh5s-S9IOEMvAIa6M5j9GW1ILNT7AcHIUfcyFcH-FF8BU_KJdRP5PXnIXFdYcylvsdoIdchy1AaUIzyiKRCU3HBYI75hez0l_F2c",
49
+ "dp": "h_sVIXW6hCCRND48EedIX06k7conMkxIu_39ErDXOWWeoMAnKIcR5TijQnviL__QxD1vQMXezuKIMHfDz2RGbClbWdD1lhtG7wvG515tDPJQXxia0wzqOQmdoFF9S8hXAAT26vPjaAAkaEZXQaxG_4Au5elgNWu6b0wDXZN1Vpk",
50
+ "dq": "GqS0YpuUTU8JGmWXUJ4HTGy7eHSpe8134V8ZdRd1oOYYHe2RX64nc25mdR24nuh3uq3Q7_9AGsYGL5E_yAl-JD9O6WUpvDE1y_wcSYty3Os0GRdUb8r8Z9kgmKDS6Pa_xTXw5eBwgfKbNlQ6zPwzgbB-x1lP-K8lbNPni3ybDR0",
51
+ "qi": "cqQfoi0sM5Su8ZOhznmdWrDIQB28H6fBKiabgaIKkbWZV4e0nwFvLquHjPOvv4Ao8iEGU5dyhvg0n5BKYPi-4mp6M6OA1sy0NrTr7EsKSYGyu2pBq9rw4oAYTM2LXKg6K-awgUUlkc451IwxHBAe15PWCBM3kvLQeijNid0Vz5I",
52
+ "key_ops": [
53
+ "sign"
54
+ ],
55
+ "ext": true,
56
+ "kid": "b41528b6f37a9500edb8a905a595bdd7"
57
+ }
58
+ ]
59
+ }
@@ -0,0 +1,9 @@
1
+ module DaVinciCRDTestKit
2
+ module CRDOptions
3
+ SMART_1 = 'smart_app_launch_1'.freeze
4
+ SMART_2 = 'smart_app_launch_2'.freeze
5
+
6
+ SMART_1_REQUIREMENT = { smart_app_launch_version: SMART_1 }.freeze
7
+ SMART_2_REQUIREMENT = { smart_app_launch_version: SMART_2 }.freeze
8
+ end
9
+ end
@@ -0,0 +1,115 @@
1
+ require_relative 'jwt_helper'
2
+ require_relative 'routes/jwk_set_endpoint_handler'
3
+ require_relative 'server_discovery_group'
4
+ require_relative 'server_hooks_group'
5
+ require_relative 'version'
6
+
7
+ module DaVinciCRDTestKit
8
+ class CRDServerSuite < Inferno::TestSuite
9
+ id :crd_server
10
+ title 'Da Vinci CRD Server Test Suite'
11
+ description <<~DESCRIPTION
12
+ The Da Vinci CRD Server Test Suite tests the conformance of server systems
13
+ to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
14
+ Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
15
+
16
+ ## Overview
17
+ This suite contains two groups of tests. The Discovery group validates a
18
+ CRD server's discovery response. The Hooks group makes CDS Hooks calls to
19
+ the server and validates the responses. By default, the hook requests are
20
+ based on examples from the CDS Hooks specification, but testers can
21
+ replace these with request bodies to elicit a particular response from
22
+ their system.
23
+
24
+ ## Trusting CDS Clients
25
+ As specified in the [CDS Hooks Spec](https://cds-hooks.hl7.org/2.0/#trusting-cds-clients),
26
+ Each time a CDS Client transmits a request to a CDS Service which
27
+ requires authentication, the request MUST include an Authorization
28
+ header presenting the JWT as a “Bearer” token:
29
+ `Authorization: Bearer {{JWT}}`
30
+
31
+ Inferno self-issues the JWT for each CDS Service call. The following
32
+ info is needed to register Inferno:
33
+
34
+ - **ISS**: `#{Inferno::Application[:base_url]}/custom/crd_server`
35
+ - **JWK Set Url**:
36
+ `#{Inferno::Application[:base_url]}/custom/crd_server/jwks.json`
37
+
38
+ ## Running the Tests
39
+ Execution of these tests require a significant amount of tester input in
40
+ the form of requests that Inferno will make against the server under test.
41
+
42
+ If you would like to try out the tests using examples from the IG and the
43
+ [CDS Hooks spec](https://cds-hooks.hl7.org/2.0/) against [the public CRD
44
+ reference server endpoint](https://crd.davinci.hl7.org/), you can do so
45
+ by:
46
+ 1. Selecting the *CRD Server RI* option from the
47
+ Preset dropdown in the upper left
48
+ 2. Clicking the *Run All Tests* button in the upper right
49
+ 3. Clicking the *Submit* button at the bottom of the input dialog
50
+
51
+ You can run these tests using your own server by updating the "CRD server
52
+ base URL" and, if needed, providing requests inputs you wish to use for
53
+ each hook your server supports.
54
+
55
+ Note that the provided inputs for these tests are not complete and systems
56
+ are not expected to pass the tests based on them.
57
+
58
+
59
+ ## Limitations
60
+ Inferno is unable to determine what requests will result in specific kinds
61
+ of responses from the server under test (e.g., what will result in
62
+ Instructions being returned vs. Coverage Information). As a result, the
63
+ tester must supply the request bodies which will cause the system under
64
+ test to return the desired response types.
65
+
66
+ The ability of a CRD Server to request additional FHIR resources is not
67
+ tested. Hook configuration is not tested.
68
+ DESCRIPTION
69
+
70
+ suite_summary <<~SUMMARY
71
+ The Da Vinci CRD Server Test Suite tests the conformance of server systems
72
+ to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
73
+ Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
74
+ SUMMARY
75
+
76
+ version VERSION
77
+
78
+ links [
79
+ {
80
+ label: 'Report Issue',
81
+ url: 'https://github.com/inferno-framework/davinci-crd-test-kit/issues'
82
+ },
83
+ {
84
+ label: 'Open Source',
85
+ url: 'https://github.com/inferno-framework/davinci-crd-test-kit'
86
+ },
87
+ {
88
+ label: 'Download',
89
+ url: 'https://github.com/inferno-framework/davinci-crd-test-kit/releases'
90
+ }
91
+ ]
92
+
93
+ input :base_url,
94
+ title: 'CRD server base URL'
95
+
96
+ fhir_resource_validator do
97
+ igs('hl7.fhir.us.davinci-crd', 'hl7.fhir.us.core')
98
+
99
+ exclude_message do |message|
100
+ message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
101
+ end
102
+ end
103
+
104
+ def inferno_base_url
105
+ suite_id = self.class.suite.id
106
+ @inferno_base_url ||= "#{Inferno::Application['base_url']}/custom/#{suite_id}"
107
+ end
108
+
109
+ route :get, '/jwks.json', Routes::JWKSetEndpointHandler
110
+
111
+ group from: :crd_server_discovery_group
112
+
113
+ group from: :crd_server_hooks
114
+ end
115
+ end
@@ -0,0 +1,22 @@
1
+ module Inferno
2
+ module DSL
3
+ module Runnable
4
+ PRE_FLIGHT_HANDLER = proc do
5
+ [
6
+ 200,
7
+ {
8
+ 'Access-Control-Allow-Origin' => '*',
9
+ 'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
10
+ },
11
+ ['']
12
+ ]
13
+ end
14
+
15
+ def allow_cors(*paths)
16
+ paths.each do |path|
17
+ route(:options, path, PRE_FLIGHT_HANDLER)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end