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,68 @@
1
+ require_relative '../urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ class EncounterStartReceiveRequestTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :crd_encounter_start_request
8
+ title 'Request received for encounter-start hook'
9
+ description %(
10
+ This test waits for an incoming [encounter-start](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#encounter-start)
11
+ hook request and responds to the client with the response types selected as an input.
12
+ )
13
+ receives_request :encounter_start
14
+
15
+ input :iss
16
+ input :encounter_start_selected_response_types,
17
+ title: 'Response types to return from encounter-start hook requests',
18
+ description: %(
19
+ Select the cards/action response types that the Inferno hook request endpoints will return. The default
20
+ response type that will be returned for this hook is the `Instructions` card type.
21
+ ),
22
+ type: 'checkbox',
23
+ default: ['instructions'],
24
+ options: {
25
+ list_options: [
26
+ {
27
+ label: 'External Reference',
28
+ value: 'external_reference'
29
+ },
30
+ {
31
+ label: 'Instructions',
32
+ value: 'instructions'
33
+ },
34
+ {
35
+ label: 'Coverage Information',
36
+ value: 'coverage_information'
37
+ },
38
+ {
39
+ label: 'Request Form Completion',
40
+ value: 'request_form_completion'
41
+ },
42
+ {
43
+ label: 'Create/Update Coverage Information',
44
+ value: 'create_update_coverage_info'
45
+ },
46
+ {
47
+ label: 'Launch SMART Application',
48
+ value: 'launch_smart_app'
49
+ }
50
+ ]
51
+ }
52
+
53
+ run do
54
+ wait(
55
+ identifier: "encounter-start #{iss}",
56
+ message: %(
57
+ **Encounter Start CDS Service Test**:
58
+
59
+ Invoke the encounter-start hook and send a request to:
60
+
61
+ `#{encounter_start_url}`
62
+
63
+ Inferno will process the request and return CDS cards if successful.
64
+ )
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../client_hook_request_validation'
2
+
3
+ module DaVinciCRDTestKit
4
+ class HookRequestOptionalFieldsTest < Inferno::Test
5
+ include DaVinciCRDTestKit::ClientHookRequestValidation
6
+
7
+ id :crd_hook_request_optional_fields
8
+ title 'Hook request contains optional fields'
9
+ description %(
10
+ Under the [CDS hooks HTTP Request section](https://cds-hooks.hl7.org/2.0/#http-request_1), the specification
11
+ requires that a CDS service request SHALL include a JSON POST body which MAY contain the following optional input
12
+ fields:
13
+ * `fhirServer` - *URL*
14
+ * `fhirAuthorization` - *object*
15
+ * `prefetch` - *object*
16
+
17
+ This test checks for the precense of these fields and if they are of the correct type. This test is optional and
18
+ will not fail if the hook request does not contain an optional field, it only produces an informational message.
19
+ If the client provides its FHIR server URL in the `fhirServer` field, and it's authorization token in the
20
+ `fhirAuthorization` field object, they will be produced as an output from this test to be used in
21
+ subsequent tests.
22
+ )
23
+ optional
24
+
25
+ output :client_fhir_server
26
+ output :client_access_token,
27
+ optional: true
28
+
29
+ uses_request :hook_request
30
+
31
+ run do
32
+ assert_valid_json(request.request_body)
33
+ request_body = JSON.parse(request.request_body)
34
+
35
+ client_fhir_server = hook_request_optional_fields_check(request_body)
36
+
37
+ output client_fhir_server: client_fhir_server[:fhir_server_uri],
38
+ client_access_token: client_fhir_server[:fhir_access_token]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../client_hook_request_validation'
2
+
3
+ module DaVinciCRDTestKit
4
+ class HookRequestRequiredFieldsTest < Inferno::Test
5
+ include DaVinciCRDTestKit::ClientHookRequestValidation
6
+ include URLs
7
+
8
+ id :crd_hook_request_required_fields
9
+ title 'Hook request contains required fields'
10
+ description %(
11
+ Under the [CDS hooks HTTP Request section](https://cds-hooks.hl7.org/2.0/#http-request_1), the specification
12
+ requires that a CDS service request SHALL include a JSON POST body with the following input fields:
13
+ * `hook` - *string*
14
+ * `hookInstance` - *string*
15
+ * `context` - *object*
16
+
17
+ Additionally, if the optional `fhirAuthorization` field is present, then the `fhirServer` field is required.
18
+
19
+ This test also checks that the `hook` field contains the correct CDS service name that the CDS client is sending
20
+ a request for
21
+ )
22
+
23
+ uses_request :hook_request
24
+
25
+ def hook_url
26
+ base_url + config.options[:hook_path]
27
+ end
28
+
29
+ def hook_name
30
+ config.options[:hook_name]
31
+ end
32
+
33
+ run do
34
+ assert_valid_json(request.request_body)
35
+ request_body = JSON.parse(request.request_body)
36
+
37
+ hook_request_required_fields_check(request_body, hook_name)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,63 @@
1
+ require_relative '../client_hook_request_validation'
2
+ module DaVinciCRDTestKit
3
+ class HookRequestValidContextTest < Inferno::Test
4
+ include URLs
5
+ include ClientHookRequestValidation
6
+
7
+ id :crd_hook_request_valid_context
8
+ title 'Hook contains valid context'
9
+ description %(
10
+ As stated in the [CDS hooks specification](https://cds-hooks.hl7.org/2.0#http-request), a CDS service request's
11
+ `context` field contains hook-specific contextual data that the CDS service will need. The context is specified
12
+ in the hook definition to guide developers on the information available at the point in the workflow when the hook
13
+ is triggered.
14
+
15
+ The `context` requirements for each [hook specified in the CRD IG](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html)
16
+ can be found below:
17
+ * [appointment-book](https://cds-hooks.hl7.org/hooks/appointment-book/2023SepSTU1Ballot/appointment-book/)
18
+ * [encounter-start](https://cds-hooks.hl7.org/hooks/encounter-start/2023SepSTU1Ballot/encounter-start/)
19
+ * [encounter-discharge](https://cds-hooks.hl7.org/hooks/encounter-discharge/2023SepSTU1Ballot/encounter-discharge/)
20
+ * [order-select](https://cds-hooks.hl7.org/hooks/order-select/2023SepSTU1Ballot/order-select/)
21
+ * [order-dispatch](https://cds-hooks.hl7.org/hooks/order-dispatch/2023SepSTU1Ballot/order-dispatch/)
22
+ * [order-sign](https://cds-hooks.org/hooks/order-sign/)
23
+
24
+ This test performs the following:
25
+ * Verifies that the incoming hook request's `context` field contains the fields required by each hook and
26
+ that they are in the correct format
27
+ * Checks the optional fields and ensures they are in the correct format
28
+ * Validates any resources contained in a `context` field that contains a Bundle or FHIR resource
29
+ * Makes FHIR requests for any `context` fields that contain an id or reference and validates each resource
30
+ response against its corresponding CRD resource profile
31
+ * Check some specific `context` requirements for hooks that have special requirements for certain fields
32
+
33
+ The client must provide its FHIR server URL and access token in the hook request in order to run
34
+ this test.
35
+ )
36
+ uses_request :hook_request
37
+
38
+ input :client_fhir_server
39
+ input :client_access_token,
40
+ optional: true
41
+
42
+ fhir_client do
43
+ url :client_fhir_server
44
+ bearer_token :client_access_token
45
+ end
46
+
47
+ def hook_name
48
+ config.options[:hook_name]
49
+ end
50
+
51
+ run do
52
+ assert_valid_json(request.request_body)
53
+ request_body = JSON.parse(request.request_body)
54
+
55
+ hook_context = request_body['context']
56
+
57
+ assert(hook_context, 'Hook request does not contain required `context` field')
58
+
59
+ hook_request_context_check(hook_context, hook_name)
60
+ no_error_validation('Context is not valid.')
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,151 @@
1
+ require_relative '../urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ class HookRequestValidPrefetchTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :crd_hook_request_valid_prefetch
8
+ title 'Hook contains valid prefetch response'
9
+ description %(
10
+ As stated in the [CDS hooks specification](https://cds-hooks.hl7.org/2.0#http-request), a CDS service request's
11
+ `prefetch` field is an optional field that contains key/value pairs of FHIR queries that the service is requesting
12
+ the CDS Client to perform and provide on each service call. The key is a string that describes the type of data
13
+ being requested and the value is a string representing the FHIR query. See [Prefetch Template](https://cds-hooks.hl7.org/2.0#prefetch-template)
14
+ for more information about how the `prefetch` formatting works.
15
+
16
+ This test verifies that the incoming hook request's `prefetch` field is in a valid JSON format and validates each
17
+ contained resource against its corresponding CRD resource profile. This test is optional and will be skipped if no
18
+ `prefetch` field is contained in the hook request.
19
+ )
20
+ optional
21
+
22
+ uses_request :hook_request
23
+
24
+ def hook_name
25
+ config.options[:hook_name]
26
+ end
27
+
28
+ def cds_services_json
29
+ JSON.parse(File.read(File.join(
30
+ __dir__, '..', 'routes', 'cds-services.json'
31
+ )))['services']
32
+ end
33
+
34
+ def structure_definition_map
35
+ {
36
+ 'Practitioner' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-practitioner',
37
+ 'PractitionerRole' => 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole',
38
+ 'Patient' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-patient',
39
+ 'Coverage' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-coverage',
40
+ 'RelatedPerson' => 'http://hl7.org/fhir/StructureDefinition/RelatedPerson',
41
+ 'Encounter' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-encounter',
42
+ 'DeviceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-devicerequest',
43
+ 'MedicationRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-medicationrequest',
44
+ 'NutritionOrder' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-nutritionorder',
45
+ 'ServiceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-servicerequest',
46
+ 'VisionPrescription' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-visionprescription'
47
+ }
48
+ end
49
+
50
+ def validate_prefetch_coverage(received_resource, advertised_prefetch_key,
51
+ received_context_patient_id, advertised_status)
52
+ assert_resource_type('Bundle', resource: received_resource)
53
+ assert(received_resource.entry.any?, 'Bundle of coverage resources received from prefetch is empty')
54
+ coverage_resource = received_resource.entry.first.resource
55
+ assert_resource_type('Coverage', resource: coverage_resource)
56
+ assert_valid_resource(resource: coverage_resource,
57
+ profile_url: structure_definition_map['Coverage'])
58
+
59
+ coverage_beneficiary_reference = coverage_resource.beneficiary
60
+ coverage_beneficiary_patient_id = coverage_beneficiary_reference.reference_id
61
+ assert(coverage_beneficiary_patient_id.present?,
62
+ "Could not get beneficiary reference id from `#{advertised_prefetch_key}` field's Coverage resource")
63
+
64
+ assert(coverage_beneficiary_patient_id == received_context_patient_id,
65
+ %(Expected `#{advertised_prefetch_key}` field's Coverage resource to have a `beneficiary` reference id of
66
+ '#{received_context_patient_id}', instead was '#{coverage_beneficiary_patient_id}'))
67
+
68
+ coverage_status = coverage_resource.status
69
+ assert(coverage_status == advertised_status,
70
+ %(Expected `#{advertised_prefetch_key}` field's Coverage resource to have a `status` of
71
+ '#{advertised_status}', instead was '#{coverage_status}'))
72
+ end
73
+
74
+ def validate_prefetch_resource(received_resource, advertised_prefetch_key, context_field_resource_type,
75
+ context_field_id)
76
+ assert_resource_type(context_field_resource_type, resource: received_resource)
77
+
78
+ if hook_name == 'order-dispatch'
79
+ assert_valid_resource(resource: received_resource)
80
+ else
81
+ assert_valid_resource(resource: received_resource,
82
+ profile_url: structure_definition_map[context_field_resource_type])
83
+ end
84
+
85
+ received_prefetch_resource_id = received_resource.id
86
+ assert(received_prefetch_resource_id.present?,
87
+ "`#{advertised_prefetch_key}` field's FHIR resource does not contain the `id` field")
88
+ assert(received_prefetch_resource_id == context_field_id,
89
+ %(Expected `#{advertised_prefetch_key}` field's FHIR resource to have an `id` of '#{context_field_id}',
90
+ instead was '#{received_prefetch_resource_id}'))
91
+ end
92
+
93
+ run do
94
+ assert_valid_json(request.request_body)
95
+ request_body = JSON.parse(request.request_body)
96
+
97
+ received_prefetch = request_body['prefetch']
98
+ received_context = request_body['context']
99
+
100
+ skip_if received_prefetch.blank?, 'Received hook request does not contain the `prefetch` field.'
101
+ skip_if received_context.blank?,
102
+ %(Received hook request does not contain the `context` field which is needed to validate the `prefetch`
103
+ field)
104
+
105
+ advertised_hook_service = cds_services_json.find { |service| service['hook'] == hook_name }
106
+
107
+ advertised_prefetch_fields = advertised_hook_service['prefetch']
108
+
109
+ advertised_prefetch_fields.each do |advertised_prefetch_key, advertised_prefetch_template|
110
+ next unless received_prefetch[advertised_prefetch_key].present?
111
+
112
+ assert(received_prefetch[advertised_prefetch_key].is_a?(Hash),
113
+ "Prefetch field `#{advertised_prefetch_key}` is not of type `Hash`.")
114
+
115
+ received_prefetch_resource = FHIR.from_contents(received_prefetch[advertised_prefetch_key].to_json)
116
+
117
+ if advertised_prefetch_template.include?('?')
118
+ advertised_prefetch_fhir_search = advertised_prefetch_template.gsub(/{|}/, '').split('?')
119
+ advertised_prefetch_resource_type = advertised_prefetch_fhir_search.first
120
+
121
+ if advertised_prefetch_resource_type == 'Coverage'
122
+ advertised_coverage_query_params = Rack::Utils.parse_nested_query(advertised_prefetch_fhir_search.last)
123
+
124
+ advertised_patient_token = advertised_coverage_query_params['patient']
125
+ advertised_context_patient_id_key = advertised_patient_token.split('.').last
126
+ received_context_patient_id = received_context[advertised_context_patient_id_key]
127
+
128
+ advertised_status_param = advertised_coverage_query_params['status']
129
+
130
+ validate_prefetch_coverage(received_prefetch_resource, advertised_prefetch_key, received_context_patient_id,
131
+ advertised_status_param)
132
+ end
133
+ else
134
+ advertised_prefetch_token = advertised_prefetch_template.gsub(/{|}/, '').split('/')
135
+ advertised_context_id = advertised_prefetch_token.last.split('.').last
136
+
137
+ if advertised_prefetch_token.length == 1
138
+ received_context_reference = FHIR::Reference.new(reference: received_context[advertised_context_id])
139
+ received_context_resource_type = received_context_reference.resource_type
140
+ received_context_id = received_context_reference.reference_id
141
+ else
142
+ received_context_id = received_context[advertised_context_id]
143
+ received_context_resource_type = advertised_prefetch_token.first
144
+ end
145
+ validate_prefetch_resource(received_prefetch_resource, advertised_prefetch_key,
146
+ received_context_resource_type, received_context_id)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,79 @@
1
+ require_relative '../urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ class OrderDispatchReceiveRequestTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :crd_order_dispatch_request
8
+ title 'Request received for order-dispatch hook'
9
+ description %(
10
+ This test waits for an incoming [order-dispatch](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#order-dispatch)
11
+ hook request and responds to the client with the response types selected as an input. This hook is a 'primary'
12
+ hook, meaning that CRD Servers SHALL, at minimum, return a [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-ext-coverage-information.html)
13
+ system action for these hooks, even if the response indicates that further information is needed or that the
14
+ level of detail provided is insufficient to determine coverage.
15
+ )
16
+ receives_request :order_dispatch
17
+
18
+ input :iss
19
+ input :order_dispatch_selected_response_types,
20
+ title: 'Response types to return from order-dispatch hook requests',
21
+ description: %(
22
+ Select the cards/action response types that the Inferno hook request endpoints will return. The default
23
+ response type that will be returned for this hook is the `Coverage Information` card type.
24
+ ),
25
+ type: 'checkbox',
26
+ default: ['coverage_information'],
27
+ options: {
28
+ list_options: [
29
+ {
30
+ label: 'External Reference',
31
+ value: 'external_reference'
32
+ },
33
+ {
34
+ label: 'Instructions',
35
+ value: 'instructions'
36
+ },
37
+ {
38
+ label: 'Coverage Information',
39
+ value: 'coverage_information'
40
+ },
41
+ {
42
+ label: 'Request Form Completion',
43
+ value: 'request_form_completion'
44
+ },
45
+ {
46
+ label: 'Create/Update Coverage Information',
47
+ value: 'create_update_coverage_info'
48
+ },
49
+ {
50
+ label: 'Launch SMART Application',
51
+ value: 'launch_smart_app'
52
+ },
53
+ {
54
+ label: 'Propose Alternate Request',
55
+ value: 'propose_alternate_request'
56
+ },
57
+ {
58
+ label: 'Additional Orders as Companions/Prerequisites',
59
+ value: 'companions_prerequisites'
60
+ }
61
+ ]
62
+ }
63
+
64
+ run do
65
+ wait(
66
+ identifier: "order-dispatch #{iss}",
67
+ message: %(
68
+ **Order Dispatch CDS Service Test**:
69
+
70
+ Invoke the order-dispatch hook and send a request to:
71
+
72
+ `#{order_dispatch_url}`
73
+
74
+ Inferno will process the request and return CDS cards if successful.
75
+ )
76
+ )
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,76 @@
1
+ require_relative '../urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ class OrderSelectReceiveRequestTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :crd_order_select_request
8
+ title 'Request received for order-select hook'
9
+ description %(
10
+ This test waits for an incoming [order-select](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#order-select)
11
+ hook request and responds to the client with the response types selected as an input.
12
+ )
13
+ receives_request :order_select
14
+
15
+ input :iss
16
+ input :order_select_selected_response_types,
17
+ title: 'Response types to return from order-select hook requests',
18
+ description: %(
19
+ Select the cards/action response types that the Inferno hook request endpoints will return. The default
20
+ response type that will be returned for this hook is the `Instructions` card type.
21
+ ),
22
+ type: 'checkbox',
23
+ default: ['instructions'],
24
+ options: {
25
+ list_options: [
26
+ {
27
+ label: 'External Reference',
28
+ value: 'external_reference'
29
+ },
30
+ {
31
+ label: 'Instructions',
32
+ value: 'instructions'
33
+ },
34
+ {
35
+ label: 'Coverage Information',
36
+ value: 'coverage_information'
37
+ },
38
+ {
39
+ label: 'Request Form Completion',
40
+ value: 'request_form_completion'
41
+ },
42
+ {
43
+ label: 'Create/Update Coverage Information',
44
+ value: 'create_update_coverage_info'
45
+ },
46
+ {
47
+ label: 'Launch SMART Application',
48
+ value: 'launch_smart_app'
49
+ },
50
+ {
51
+ label: 'Propose Alternate Request',
52
+ value: 'propose_alternate_request'
53
+ },
54
+ {
55
+ label: 'Additional Orders as Companions/Prerequisites',
56
+ value: 'companions_prerequisites'
57
+ }
58
+ ]
59
+ }
60
+
61
+ run do
62
+ wait(
63
+ identifier: "order-select #{iss}",
64
+ message: %(
65
+ **Order Select CDS Service Test**:
66
+
67
+ Invoke the order-select hook and send a request to:
68
+
69
+ `#{order_select_url}`
70
+
71
+ Inferno will process the request and return CDS cards if successful.
72
+ )
73
+ )
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,79 @@
1
+ require_relative '../urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ class OrderSignReceiveRequestTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :crd_order_sign_request
8
+ title 'Request received for order-sign hook'
9
+ description %(
10
+ This test waits for an incoming [order-sign](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#order-sign)
11
+ hook request and responds to the client with the response types selected as an input. This hook is a 'primary'
12
+ hook, meaning that CRD Servers SHALL, at minimum, return a [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-ext-coverage-information.html)
13
+ system action for these hooks, even if the response indicates that further information is needed or that the
14
+ level of detail provided is insufficient to determine coverage.
15
+ )
16
+ receives_request :order_sign
17
+
18
+ input :iss
19
+ input :order_sign_selected_response_types,
20
+ title: 'Response types to return from order-sign hook requests',
21
+ description: %(
22
+ Select the cards/action response types that the Inferno hook request endpoints will return. The default
23
+ response type that will be returned for this hook is the `Coverage Information` card type.
24
+ ),
25
+ type: 'checkbox',
26
+ default: ['coverage_information'],
27
+ options: {
28
+ list_options: [
29
+ {
30
+ label: 'External Reference',
31
+ value: 'external_reference'
32
+ },
33
+ {
34
+ label: 'Instructions',
35
+ value: 'instructions'
36
+ },
37
+ {
38
+ label: 'Coverage Information',
39
+ value: 'coverage_information'
40
+ },
41
+ {
42
+ label: 'Request Form Completion',
43
+ value: 'request_form_completion'
44
+ },
45
+ {
46
+ label: 'Create/Update Coverage Information',
47
+ value: 'create_update_coverage_info'
48
+ },
49
+ {
50
+ label: 'Launch SMART Application',
51
+ value: 'launch_smart_app'
52
+ },
53
+ {
54
+ label: 'Propose Alternate Request',
55
+ value: 'propose_alternate_request'
56
+ },
57
+ {
58
+ label: 'Additional Orders as Companions/Prerequisites',
59
+ value: 'companions_prerequisites'
60
+ }
61
+ ]
62
+ }
63
+
64
+ run do
65
+ wait(
66
+ identifier: "order-sign #{iss}",
67
+ message: %(
68
+ **Order Sign CDS Service Test**:
69
+
70
+ Invoke the order-sign hook and send a request to:
71
+
72
+ `#{order_sign_url}`
73
+
74
+ Inferno will process the request and return CDS cards if successful.
75
+ )
76
+ )
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,65 @@
1
+ module DaVinciCRDTestKit
2
+ class RetrieveJWKSTest < Inferno::Test
3
+ id :crd_retrieve_jwks
4
+ title 'JWKS can be retrieved'
5
+ description %(
6
+ Verify that the JWKS can be retrieved from the JWKS uri if it is present in the `jku` field within the JWT token
7
+ header. As per the [CDS hooks specification](https://cds-hooks.hl7.org/2.0#trusting-cds-clients), if the jku
8
+ header field is ommitted, the CDS Client and CDS Service SHALL communicate the JWK Set out-of-band. Therefore,
9
+ if the client does not make their keys publicly available via a uri in the `jku` field, the user must
10
+ submit the jwk_set as an input to the test.
11
+ )
12
+
13
+ input :auth_token_header_json
14
+ input :jwk_set,
15
+ title: "The Client's JWK Set containing it's public key",
16
+ description: %(
17
+ Must supply if you do not make your keys publicly available via a uri in the authorization JWT header `jku`
18
+ field'
19
+ ),
20
+ type: 'textarea',
21
+ optional: true
22
+ output :crd_jwks_json, :crd_jwks_keys_json
23
+ makes_request :crd_client_jwks
24
+
25
+ run do
26
+ token_header = JSON.parse(auth_token_header_json)
27
+ jku = token_header['jku']
28
+
29
+ if jku.present?
30
+ get(jku, name: :crd_client_jwks)
31
+
32
+ assert_response_status(200)
33
+ assert_valid_json(response[:body])
34
+ output crd_jwks_json: response[:body]
35
+
36
+ jwks = JSON.parse(response[:body])
37
+ else
38
+ skip_if jwk_set.blank?,
39
+ %(JWK Set must be inputted if Client's JWK Set is not available via a URL identified by the jku header
40
+ field)
41
+
42
+ jwks = JSON.parse(jwk_set)
43
+ end
44
+
45
+ keys = jwks['keys']
46
+ assert keys.is_a?(Array), 'JWKS `keys` field must be an array'
47
+
48
+ assert keys.present?, 'The JWK set returned contains no public keys'
49
+
50
+ keys.each do |jwk|
51
+ JWT::JWK.import(jwk.deep_symbolize_keys)
52
+ rescue StandardError
53
+ assert false, "Invalid JWK: #{jwk.to_json}"
54
+ end
55
+
56
+ kid_presence = keys.all? { |key| key['kid'].present? }
57
+ assert kid_presence, '`kid` field must be present in each key if JWKS contains multiple keys'
58
+
59
+ kid_uniqueness = keys.map { |key| key['kid'] }.uniq.length == keys.length
60
+ assert kid_uniqueness, '`kid` must be unique within the client\' JWK Set.'
61
+
62
+ output crd_jwks_keys_json: keys.to_json
63
+ end
64
+ end
65
+ end