onc_certification_g10_test_kit 2.0.0.rc1

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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/inferno/exceptions.rb +31 -0
  4. data/lib/inferno/ext/bloomer.rb +24 -0
  5. data/lib/inferno/repositiories/validators.rb +17 -0
  6. data/lib/inferno/repositiories/value_sets.rb +26 -0
  7. data/lib/inferno/terminology/bcp47.rb +95 -0
  8. data/lib/inferno/terminology/bcp_13.rb +26 -0
  9. data/lib/inferno/terminology/codesystem.rb +49 -0
  10. data/lib/inferno/terminology/expected_manifest.yml +1123 -0
  11. data/lib/inferno/terminology/fhir_package_manager.rb +69 -0
  12. data/lib/inferno/terminology/loader.rb +298 -0
  13. data/lib/inferno/terminology/tasks/check_built_terminology.rb +77 -0
  14. data/lib/inferno/terminology/tasks/cleanup.rb +13 -0
  15. data/lib/inferno/terminology/tasks/cleanup_precursors.rb +23 -0
  16. data/lib/inferno/terminology/tasks/count_codes_in_value_set.rb +20 -0
  17. data/lib/inferno/terminology/tasks/create_value_set_validators.rb +34 -0
  18. data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +27 -0
  19. data/lib/inferno/terminology/tasks/download_umls.rb +109 -0
  20. data/lib/inferno/terminology/tasks/download_umls_notice.rb +20 -0
  21. data/lib/inferno/terminology/tasks/expand_value_set_to_file.rb +36 -0
  22. data/lib/inferno/terminology/tasks/process_umls.rb +91 -0
  23. data/lib/inferno/terminology/tasks/process_umls_translations.rb +85 -0
  24. data/lib/inferno/terminology/tasks/run_umls_jar.rb +75 -0
  25. data/lib/inferno/terminology/tasks/temp_dir.rb +27 -0
  26. data/lib/inferno/terminology/tasks/unzip_umls.rb +42 -0
  27. data/lib/inferno/terminology/tasks/validate_code.rb +36 -0
  28. data/lib/inferno/terminology/tasks.rb +11 -0
  29. data/lib/inferno/terminology/terminology_configuration.rb +52 -0
  30. data/lib/inferno/terminology/terminology_validation.rb +42 -0
  31. data/lib/inferno/terminology/validator.rb +64 -0
  32. data/lib/inferno/terminology/value_set.rb +462 -0
  33. data/lib/inferno/terminology.rb +16 -0
  34. data/lib/onc_certification_g10_test_kit/authorization_request_builder.rb +87 -0
  35. data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +48 -0
  36. data/lib/onc_certification_g10_test_kit/bulk_data_authorization.rb +235 -0
  37. data/lib/onc_certification_g10_test_kit/bulk_data_group_export.rb +255 -0
  38. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +474 -0
  39. data/lib/onc_certification_g10_test_kit/bulk_data_jwks.json +58 -0
  40. data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +171 -0
  41. data/lib/onc_certification_g10_test_kit/configuration_checker.rb +104 -0
  42. data/lib/onc_certification_g10_test_kit/export_kick_off_performer.rb +12 -0
  43. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json +3772 -0
  44. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodytemp.json +3772 -0
  45. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyweight.json +3772 -0
  46. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bp.json +6034 -0
  47. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-heartrate.json +3756 -0
  48. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-resprate.json +3756 -0
  49. data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +66 -0
  50. data/lib/onc_certification_g10_test_kit/multi_patient_api.rb +43 -0
  51. data/lib/onc_certification_g10_test_kit/patient_context_test.rb +30 -0
  52. data/lib/onc_certification_g10_test_kit/profile_guesser.rb +69 -0
  53. data/lib/onc_certification_g10_test_kit/resource_access_test.rb +96 -0
  54. data/lib/onc_certification_g10_test_kit/restricted_access_test.rb +12 -0
  55. data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +303 -0
  56. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +136 -0
  57. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +209 -0
  58. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +197 -0
  59. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +123 -0
  60. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +113 -0
  61. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +153 -0
  62. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +177 -0
  63. data/lib/onc_certification_g10_test_kit/terminology_binding_validator.rb +140 -0
  64. data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +133 -0
  65. data/lib/onc_certification_g10_test_kit/unauthorized_access_test.rb +25 -0
  66. data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +375 -0
  67. data/lib/onc_certification_g10_test_kit/version.rb +3 -0
  68. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +470 -0
  69. data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +37 -0
  70. data/lib/onc_certification_g10_test_kit.rb +223 -0
  71. metadata +310 -0
@@ -0,0 +1,235 @@
1
+ require_relative 'authorization_request_builder'
2
+
3
+ module ONCCertificationG10TestKit
4
+ class BulkDataAuthorization < Inferno::TestGroup
5
+ title 'Bulk Data Authorization'
6
+ short_description 'Demonstrate SMART Backend Services Authorization for Bulk Data.'
7
+ description <<~DESCRIPTION
8
+ Bulk Data servers are required to authorize clients using the
9
+ [Backend Service Authorization](http://hl7.org/fhir/uv/bulkdata/STU1/authorization/)
10
+ specification as defined in the [FHIR Bulk Data Access IG v1.0.0](http://hl7.org/fhir/uv/bulkdata/STU1/).
11
+
12
+ In this set of tests, Inferno serves as a Bulk Data client that requests authorization
13
+ from the Bulk Data authorization server. It also performs a number of negative tests
14
+ to validate that the authorization service does not improperly authorize invalid
15
+ requests.
16
+
17
+ This test returns an access token.
18
+ DESCRIPTION
19
+
20
+ id :bulk_data_authorization
21
+
22
+ input :bulk_token_endpoint,
23
+ title: 'Backend Services Token Endpoint',
24
+ description: 'The OAuth 2.0 Token Endpoint used by the Backend Services specification
25
+ to provide bearer tokens.'
26
+ input :bulk_client_id,
27
+ title: 'Bulk Data Client ID',
28
+ description: 'Client ID provided at registration to the Inferno application.'
29
+ input :bulk_scope,
30
+ title: 'Bulk Data Scopes',
31
+ description: 'Bulk Data Scopes provided at registration to the Inferno application.',
32
+ default: 'system/*.read'
33
+ input :bulk_encryption_method,
34
+ title: 'Encryption Method',
35
+ description: 'The server is required to suport either ES384 or RS384 encryption methods
36
+ for JWT signature verification. Select which method to use.',
37
+ type: 'radio',
38
+ default: 'ES384',
39
+ options: {
40
+ list_options: [
41
+ {
42
+ label: 'ES384',
43
+ value: 'ES384'
44
+ },
45
+ {
46
+ label: 'RS384',
47
+ value: 'RS384'
48
+ }
49
+ ]
50
+ }
51
+ output :bearer_token
52
+
53
+ http_client :token_endpoint do
54
+ url :bulk_token_endpoint
55
+ end
56
+
57
+ test from: :tls_version_test do
58
+ title 'Authorization service token endpoint secured by transport layer security'
59
+ description <<~DESCRIPTION
60
+ [§170.315(g)(10) Test
61
+ Procedure](https://www.healthit.gov/test-method/standardized-api-patient-and-population-services)
62
+ requires that all exchanges described herein between a client and a
63
+ server SHALL be secured using Transport Layer Security (TLS) Protocol
64
+ Version 1.2 (RFC5246).
65
+ DESCRIPTION
66
+ id :g10_bulk_token_tls_version
67
+
68
+ config(
69
+ inputs: { url: { name: :bulk_token_endpoint } },
70
+ options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
71
+ )
72
+ end
73
+
74
+ test do
75
+ title 'Authorization request fails when client supplies invalid grant_type'
76
+ description <<~DESCRIPTION
77
+ The Backend Service Authorization specification defines the required fields for the
78
+ authorization request, made via HTTP POST to authorization token endpoint.
79
+ This includes the `grant_type` parameter, where the value must be `client_credentials`.
80
+
81
+ The OAuth 2.0 Authorization Framework describes the proper response for an
82
+ invalid request in the client credentials grant flow:
83
+
84
+ ```
85
+ If the request failed client authentication or is invalid, the authorization server returns an
86
+ error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
87
+ ```
88
+ DESCRIPTION
89
+ # link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#protocol-details'
90
+
91
+ run do
92
+ post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
93
+ scope: bulk_scope,
94
+ iss: bulk_client_id,
95
+ sub: bulk_client_id,
96
+ aud: bulk_token_endpoint,
97
+ grant_type: 'not_a_grant_type')
98
+
99
+ post({ client: :token_endpoint }.merge(post_request_content))
100
+
101
+ assert_response_status(400)
102
+ end
103
+ end
104
+
105
+ test do
106
+ title 'Authorization request fails when supplied invalid client_assertion_type'
107
+ description <<~DESCRIPTION
108
+ The Backend Service Authorization specification defines the required fields for the
109
+ authorization request, made via HTTP POST to authorization token endpoint.
110
+ This includes the `client_assertion_type` parameter, where the value must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
111
+
112
+ The OAuth 2.0 Authorization Framework describes the proper response for an
113
+ invalid request in the client credentials grant flow:
114
+
115
+ ```
116
+ If the request failed client authentication or is invalid, the authorization server returns an
117
+ error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
118
+ ```
119
+ DESCRIPTION
120
+ # link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#protocol-details'
121
+
122
+ run do
123
+ post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
124
+ scope: bulk_scope,
125
+ iss: bulk_client_id,
126
+ sub: bulk_client_id,
127
+ aud: bulk_token_endpoint,
128
+ client_assertion_type: 'not_an_assertion_type')
129
+
130
+ post({ client: :token_endpoint }.merge(post_request_content))
131
+
132
+ assert_response_status(400)
133
+ end
134
+ end
135
+
136
+ test do
137
+ title 'Authorization request fails when client supplies invalid JWT token'
138
+ description <<~DESCRIPTION
139
+ The Backend Service Authorization specification defines the required fields for the
140
+ authorization request, made via HTTP POST to authorization token endpoint.
141
+ This includes the `client_assertion` parameter, where the value must be
142
+ a valid JWT. The JWT SHALL include the following claims, and SHALL be signed with the client’s private key.
143
+
144
+ | JWT Claim | Required? | Description |
145
+ | --- | --- | --- |
146
+ | iss | required | Issuer of the JWT -- the client's client_id, as determined during registration with the FHIR authorization server (note that this is the same as the value for the sub claim) |
147
+ | sub | required | The service's client_id, as determined during registration with the FHIR authorization server (note that this is the same as the value for the iss claim) |
148
+ | aud | required | The FHIR authorization server's "token URL" (the same URL to which this authentication JWT will be posted) |
149
+ | exp | required | Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). This time SHALL be no more than five minutes in the future. |
150
+ | jti | required | A nonce string value that uniquely identifies this authentication JWT. |
151
+
152
+ The OAuth 2.0 Authorization Framework describes the proper response for an
153
+ invalid request in the client credentials grant flow:
154
+
155
+ ```
156
+ If the request failed client authentication or is invalid, the authorization server returns an
157
+ error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
158
+ ```
159
+ DESCRIPTION
160
+ # link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#protocol-details'
161
+
162
+ run do
163
+ post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
164
+ scope: bulk_scope,
165
+ iss: 'not_a_valid_iss',
166
+ sub: bulk_client_id,
167
+ aud: bulk_token_endpoint)
168
+
169
+ post({ client: :token_endpoint }.merge(post_request_content))
170
+
171
+ assert_response_status([400, 401])
172
+ end
173
+ end
174
+
175
+ test do
176
+ title 'Authorization request succeeds when supplied correct information'
177
+ description <<~DESCRIPTION
178
+ If the access token request is valid and authorized, the authorization server SHALL issue an access token in response.
179
+ DESCRIPTION
180
+ # link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#issuing-access-tokens'
181
+
182
+ output :authentication_response
183
+
184
+ run do
185
+ post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
186
+ scope: bulk_scope,
187
+ iss: bulk_client_id,
188
+ sub: bulk_client_id,
189
+ aud: bulk_token_endpoint)
190
+
191
+ authentication_response = post({ client: :token_endpoint }.merge(post_request_content))
192
+
193
+ assert_response_status([200, 201])
194
+
195
+ output authentication_response: authentication_response.response_body
196
+ end
197
+ end
198
+
199
+ test do
200
+ title 'Authorization request response body contains required information encoded in JSON'
201
+ description <<~DESCRIPTION
202
+ The access token response SHALL be a JSON object with the following properties:
203
+
204
+ | Token Property | Required? | Description |
205
+ | --- | --- | --- |
206
+ | access_token | required | The access token issued by the authorization server. |
207
+ | token_type | required | Fixed value: bearer. |
208
+ | expires_in | required | The lifetime in seconds of the access token. The recommended value is 300, for a five-minute token lifetime. |
209
+ | scope | required | Scope of access authorized. Note that this can be different from the scopes requested by the app. |
210
+ DESCRIPTION
211
+ # link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#issuing-access-tokens'
212
+
213
+ input :authentication_response
214
+ output :bearer_token
215
+
216
+ run do
217
+ skip_if authentication_response.blank?, 'No authentication response received.'
218
+
219
+ assert_valid_json(authentication_response)
220
+ response_body = JSON.parse(authentication_response)
221
+
222
+ access_token = response_body['access_token']
223
+ assert access_token.present?, 'Token response did not contain access_token as required'
224
+
225
+ output bearer_token: access_token
226
+
227
+ required_keys = ['token_type', 'expires_in', 'scope']
228
+
229
+ required_keys.each do |key|
230
+ assert response_body[key].present?, "Token response did not contain #{key} as required"
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,255 @@
1
+ require_relative 'export_kick_off_performer'
2
+
3
+ module ONCCertificationG10TestKit
4
+ class BulkDataGroupExport < Inferno::TestGroup
5
+ title 'Group Compartment Export Tests'
6
+ short_description 'Verify that the system supports Group compartment export.'
7
+ description <<~DESCRIPTION
8
+ Verify that system level export on the Bulk Data server follow the Bulk Data Access Implementation Guide
9
+ DESCRIPTION
10
+
11
+ id :bulk_data_group_export
12
+
13
+ input :bearer_token
14
+ input :bulk_server_url,
15
+ title: 'Bulk Data FHIR URL',
16
+ description: 'The URL of the Bulk FHIR server.'
17
+ input :group_id,
18
+ title: 'Group ID',
19
+ description: 'The Group ID associated with the group of patients to be exported.'
20
+ input :bulk_timeout,
21
+ title: 'Export Times Out after (1-600)',
22
+ description: 'While testing, Inferno waits for the server to complete the exporting task.
23
+ If the calculated totalTime is greater than the timeout value specified here,
24
+ Inferno bulk client stops testing. Please enter an integer for the maximum wait time in seconds.
25
+ If timeout is less than 1, Inferno uses default value 180.
26
+ If the timeout is greater than 600 (10 minutes), Inferno uses the maximum value 600.',
27
+ default: 180
28
+
29
+ output :requires_access_token, :status_output, :bulk_download_url
30
+
31
+ fhir_client :bulk_server do
32
+ url :bulk_server_url
33
+ end
34
+
35
+ http_client :bulk_server do
36
+ url :bulk_server_url
37
+ end
38
+
39
+ test from: :tls_version_test do
40
+ title 'Bulk Data Server is secured by transport layer security'
41
+ description <<~DESCRIPTION
42
+ [§170.315(g)(10) Test
43
+ Procedure](https://www.healthit.gov/test-method/standardized-api-patient-and-population-services)
44
+ requires that all exchanges described herein between a client and a
45
+ server SHALL be secured using Transport Layer Security (TLS) Protocol
46
+ Version 1.2 (RFC5246).
47
+ DESCRIPTION
48
+ id :g10_bulk_data_server_tls_version
49
+
50
+ config(
51
+ inputs: { url: { name: :bulk_server_url } },
52
+ options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
53
+ )
54
+ end
55
+
56
+ test do
57
+ title 'Bulk Data Server declares support for Group export operation in CapabilityStatement'
58
+ description <<~DESCRIPTION
59
+ The Bulk Data Server SHALL declare support for Group/[id]/$export operation in its server CapabilityStatement
60
+ DESCRIPTION
61
+ # link 'http://hl7.org/fhir/uv/bulkdata/OperationDefinition-group-export.html'
62
+
63
+ run do
64
+ fhir_get_capability_statement(client: :bulk_server)
65
+ assert_response_status([200, 201])
66
+
67
+ assert_valid_json(request.response_body)
68
+ capability_statement = FHIR.from_contents(request.response_body)
69
+
70
+ has_export_operation = capability_statement&.rest&.any? do |rest|
71
+ rest.resource&.any? do |resource|
72
+ resource.type == 'Group' &&
73
+ resource.respond_to?(:operation) &&
74
+ resource.operation&.find do |operation|
75
+ operation.definition&.match(%r{^http://hl7.org/fhir/uv/bulkdata/OperationDefinition/group-export(\|\S+)?})
76
+ end
77
+ end
78
+ end
79
+
80
+ assert has_export_operation,
81
+ 'Server CapabilityStatement did not declare support for export operation in Group resource'
82
+ end
83
+ end
84
+
85
+ test do
86
+ title 'Bulk Data Server rejects $export request without authorization'
87
+ description <<~DESCRIPTION
88
+ The FHIR server SHALL limit the data returned to only those FHIR resources for which the client is authorized.
89
+
90
+ [FHIR R4 Security](http://build.fhir.org/security.html#AccessDenied) and
91
+ [The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-3.1)
92
+ recommend using HTTP status code 401 for invalid token but also allow the actual result be controlled by policy and context.
93
+ DESCRIPTION
94
+ # link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#bulk-data-kick-off-request'
95
+
96
+ include ExportKickOffPerformer
97
+
98
+ run do
99
+ skip_if bearer_token.blank?, 'Could not verify this functionality when bearer token is not set'
100
+
101
+ perform_export_kick_off_request(use_token: false)
102
+ assert_response_status([400, 401])
103
+ end
104
+ end
105
+
106
+ test do
107
+ title 'Bulk Data Server returns "202 Accepted" and "Content-location" for $export operation'
108
+ description <<~DESCRIPTION
109
+ Response - Success
110
+
111
+ * HTTP Status Code of 202 Accepted
112
+ * Content-Location header with the absolute URL of an endpoint for subsequent status requests (polling location)
113
+ DESCRIPTION
114
+ # link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#response---success'
115
+
116
+ include ExportKickOffPerformer
117
+
118
+ output :polling_url
119
+
120
+ run do
121
+ perform_export_kick_off_request
122
+ assert_response_status(202)
123
+
124
+ polling_url = request.response_header('content-location')&.value
125
+ assert polling_url.present?, 'Export response headers did not include "Content-Location"'
126
+
127
+ output polling_url: polling_url
128
+ end
129
+ end
130
+
131
+ test do
132
+ title 'Bulk Data Server returns "202 Accepted" or "200 OK" for status check'
133
+ description <<~DESCRIPTION
134
+ Clients SHOULD follow an exponential backoff approach when polling for status. Servers SHOULD respond with
135
+
136
+ * In-Progress Status: HTTP Status Code of 202 Accepted
137
+ * Complete Status: HTTP status of 200 OK and Content-Type header of application/json
138
+
139
+ The JSON object of Complete Status SHALL contain these required field:
140
+
141
+ * transactionTime, request, requiresAccessToken, output, and error
142
+ DESCRIPTION
143
+ # link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#bulk-data-status-request'
144
+
145
+ input :polling_url
146
+
147
+ output :status_response, :requires_access_token
148
+
149
+ run do
150
+ skip 'Server response did not have Content-Location in header' unless polling_url.present?
151
+
152
+ timeout = bulk_timeout.to_i
153
+
154
+ if !timeout.positive?
155
+ timeout = 180
156
+ elsif timeout > 600
157
+ timeout = 600
158
+ end
159
+
160
+ wait_time = 1
161
+ start = Time.now
162
+
163
+ loop do
164
+ get(polling_url, headers: { authorization: "Bearer #{bearer_token}" })
165
+
166
+ retry_after_val = request.response_header('retry-after')&.value.to_i
167
+
168
+ wait_time = retry_after_val.positive? ? retry_after_val : wait_time *= 2
169
+
170
+ seconds_used = Time.now - start + wait_time
171
+
172
+ break if response[:status] != 202 || seconds_used > timeout
173
+
174
+ sleep wait_time
175
+ end
176
+
177
+ skip "Server took more than #{timeout} seconds to process the request." if response[:status] == 202
178
+ assert_response_status(200)
179
+
180
+ assert request.response_header('content-type')&.value&.include?('application/json'),
181
+ 'Content-Type not application/json'
182
+
183
+ response_body = JSON.parse(response[:body])
184
+
185
+ ['transactionTime', 'request', 'requiresAccessToken', 'output', 'error'].each do |key|
186
+ assert response_body.key?(key), "Complete Status response did not contain \"#{key}\" as required"
187
+ end
188
+
189
+ output requires_access_token: response_body['requiresAccessToken'].to_s.downcase
190
+ output status_response: response[:body]
191
+ end
192
+ end
193
+
194
+ test do
195
+ title 'Bulk Data Server returns output with type and url for status complete'
196
+ description <<~DESCRIPTION
197
+ The value of output field is an array of file items with one entry for each generated file.
198
+ If no resources are returned from the kick-off request, the server SHOULD return an empty array.
199
+
200
+ Each file item SHALL contain the following fields:
201
+
202
+ * type - the FHIR resource type that is contained in the file.
203
+
204
+ Each file SHALL contain resources of only one type, but a server MAY create more than one file for each resource type returned.
205
+
206
+ * url - the path to the file. The format of the file SHOULD reflect that requested in the _outputFormat parameter of the initial kick-off request.
207
+ DESCRIPTION
208
+ # link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#response---complete-status'
209
+
210
+ input :status_response
211
+
212
+ output :status_output, :bulk_download_url
213
+
214
+ run do
215
+ assert status_response.present?, 'Bulk Data Server status response not found'
216
+
217
+ status_output = JSON.parse(status_response)['output']
218
+ assert status_output, 'Bulk Data Server status response does not contain output'
219
+
220
+ output status_output: status_output.to_json,
221
+ bulk_download_url: status_output[0]['url']
222
+
223
+ status_output.each do |file|
224
+ ['type', 'url'].each do |key|
225
+ assert file.key?(key), "Output file did not contain \"#{key}\" as required"
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ test do
232
+ title 'Bulk Data Server returns "202 Accepted" for delete request'
233
+ description <<~DESCRIPTION
234
+ After a bulk data request has been started, a client MAY send a delete request to the URL provided in the Content-Location header to cancel the request.
235
+ Bulk Data Server MUST support client's delete request and return HTTP Status Code of "202 Accepted"
236
+ DESCRIPTION
237
+ # link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#bulk-data-delete-request'
238
+
239
+ include ExportKickOffPerformer
240
+
241
+ run do
242
+ perform_export_kick_off_request
243
+ assert_response_status(202)
244
+
245
+ polling_url = request.response_header('content-location')&.value
246
+ assert polling_url.present?, 'Export response header did not include "Content-Location"'
247
+
248
+ headers = { accept: 'application/json', authorization: "Bearer #{bearer_token}" }
249
+
250
+ delete(polling_url, headers: headers)
251
+ assert_response_status(202)
252
+ end
253
+ end
254
+ end
255
+ end