davinci_crd_test_kit 0.9.0

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