subscriptions_test_kit 0.12.2 → 0.13.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.
- checksums.yaml +4 -4
- data/lib/subscriptions_test_kit/common/notification_conformance_verification.rb +6 -4
- data/lib/subscriptions_test_kit/common/subscription_conformance_verification.rb +4 -2
- data/lib/subscriptions_test_kit/docs/subscriptions_r5_backport_r4_client_suite_description.md +39 -18
- data/lib/subscriptions_test_kit/docs/subscriptions_r5_backport_r4_server_suite_description.md +16 -4
- data/lib/subscriptions_test_kit/endpoints/subscription_create_endpoint.rb +5 -33
- data/lib/subscriptions_test_kit/endpoints/subscription_delete_endpoint.rb +38 -0
- data/lib/subscriptions_test_kit/endpoints/subscription_patch_endpoint.rb +69 -0
- data/lib/subscriptions_test_kit/endpoints/subscription_read_endpoint.rb +5 -5
- data/lib/subscriptions_test_kit/endpoints/subscription_status_endpoint.rb +12 -17
- data/lib/subscriptions_test_kit/endpoints/subscription_update_endpoint.rb +81 -0
- data/lib/subscriptions_test_kit/fhir_validation.rb +9 -0
- data/lib/subscriptions_test_kit/requirements/generated/subscriptions_r5_backport_r4_server_requirements_coverage.csv +2 -2
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/common/subscription_simulation_utils.rb +105 -11
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/fixtures/capability_statement.json +12 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow/conformance_verification/delete_verification_test.rb +33 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow/conformance_verification/patch_update_verification_test.rb +44 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow/conformance_verification/put_update_verification_test.rb +36 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow/conformance_verification/update_request_loading.rb +61 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow/update_delete_conformance_verification_group.rb +18 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow/update_delete_interaction_test.rb +95 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/update_delete_workflow_group.rb +40 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification/notification_input_verification_test.rb +3 -1
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification/processing_attestation_test.rb +1 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client_suite.rb +24 -2
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/interaction/subscription_conformance_test.rb +3 -2
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/subscription_creation.rb +1 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/subscription_update_delete.rb +43 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/update_delete/delete_test.rb +30 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/update_delete/update_patch_test.rb +33 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/update_delete/update_put_test.rb +32 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/update_delete_group.rb +27 -0
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/workflow_group.rb +6 -2
- data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server_suite.rb +18 -2
- data/lib/subscriptions_test_kit/tags.rb +2 -0
- data/lib/subscriptions_test_kit/version.rb +2 -2
- metadata +36 -6
|
@@ -7,9 +7,16 @@ module SubscriptionsTestKit
|
|
|
7
7
|
# application/json, so we'll allow that. Disallow XML for now.
|
|
8
8
|
DEFAULT_MIME_TYPE = 'application/fhir+json'
|
|
9
9
|
ALLOWED_MIME_TYPES = [DEFAULT_MIME_TYPE, 'application/json'].freeze
|
|
10
|
+
JSON_PATCH_MIME_TYPE = 'application/json-patch+json'
|
|
10
11
|
|
|
11
12
|
def notification_bundle_input(test_result)
|
|
12
|
-
JSON.parse(test_result.input_json).find { |i| i['name'] == 'notification_bundle' }
|
|
13
|
+
input = JSON.parse(test_result.input_json).find { |i| i['name'] == 'notification_bundle' }
|
|
14
|
+
return input['value'] if input.present?
|
|
15
|
+
|
|
16
|
+
# Tests that wait after the Subscription workflow (e.g., update and delete) do not take the notification
|
|
17
|
+
# bundle as an input, so fall back to the value stored for the session.
|
|
18
|
+
Inferno::Repositories::SessionData.new.load(test_session_id: test_result.test_session_id,
|
|
19
|
+
name: 'notification_bundle')
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
def client_access_token_input(test_result)
|
|
@@ -69,19 +76,63 @@ module SubscriptionsTestKit
|
|
|
69
76
|
end
|
|
70
77
|
return unless request
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
parse_subscription(request.response_body, as_json:)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The created Subscription as modified by any subsequent successful updates, or nil if it has been deleted.
|
|
83
|
+
# Updates and deletes are only accepted during the update and delete interaction test (see
|
|
84
|
+
# subscription_change_allowed?), so each run of that test starts again from the created Subscription.
|
|
85
|
+
def find_current_subscription(test_session_id)
|
|
86
|
+
subscription = find_subscription(test_session_id)
|
|
87
|
+
return if subscription.nil?
|
|
88
|
+
|
|
89
|
+
# Only updates and deletes are tagged with the Subscription id, so load them together
|
|
90
|
+
delete_requests, update_requests = requests_repo.tagged_requests(test_session_id, [subscription.id])
|
|
91
|
+
.partition { |r| r.tags.include?(SUBSCRIPTION_DELETE_TAG) }
|
|
92
|
+
return if delete_requests.any? { |r| r.status.between?(200, 299) }
|
|
93
|
+
|
|
94
|
+
update_request = update_requests.reverse.find { |r| r.status == 200 }
|
|
95
|
+
return subscription unless update_request
|
|
96
|
+
|
|
97
|
+
parse_subscription(update_request.response_body)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# For endpoints: whether the waiting test accepts changes (updates and deletes) to the Subscription, which
|
|
101
|
+
# a test indicates with the accepts_subscription_changes config option
|
|
102
|
+
def subscription_change_allowed?
|
|
103
|
+
test.config.options[:accepts_subscription_changes] == true
|
|
82
104
|
end
|
|
83
105
|
|
|
106
|
+
# For endpoints: reject a change to the Subscription requested while a different test is waiting
|
|
107
|
+
def reject_subscription_change
|
|
108
|
+
fhir_error_response(422, 'business-rule', %(
|
|
109
|
+
Inferno only accepts Subscription updates and deletes during the Demonstrate Subscription Update and
|
|
110
|
+
Deletion tests.
|
|
111
|
+
).squish)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# For endpoints: respond with an error status and an OperationOutcome describing the error
|
|
115
|
+
def fhir_error_response(status, code, text = nil)
|
|
116
|
+
response.format = :json
|
|
117
|
+
response.status = status
|
|
118
|
+
response.body = operation_outcome('error', code, text).to_json
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Inferno does not act on status changes requested by the client, except to honor requests to turn
|
|
122
|
+
# the Subscription off
|
|
123
|
+
def current_subscription_status_code(subscription)
|
|
124
|
+
return 'off' if subscription.status == 'off'
|
|
125
|
+
|
|
126
|
+
determine_subscription_status_code(subscription.id)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Memoized because the handshakes do not change while an endpoint responds to a request
|
|
84
130
|
def determine_subscription_status_code(subscription_id)
|
|
131
|
+
@subscription_status_codes ||= {}
|
|
132
|
+
@subscription_status_codes[subscription_id] ||= handshake_status_code(subscription_id)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def handshake_status_code(subscription_id)
|
|
85
136
|
handshakes = requests_repo.tagged_requests(test_run.test_session_id, [REST_HOOK_HANDSHAKE_NOTIFICATION_TAG])
|
|
86
137
|
handshake = handshakes.filter { |h| notification_subscription_id(h.request_body) == subscription_id }.last
|
|
87
138
|
|
|
@@ -106,8 +157,51 @@ module SubscriptionsTestKit
|
|
|
106
157
|
end
|
|
107
158
|
end
|
|
108
159
|
|
|
160
|
+
# Checks that Inferno is able to simulate the requested Subscription.
|
|
161
|
+
# Returns an OperationOutcome describing the problem, or nil if the Subscription is supported.
|
|
162
|
+
def verify_subscription(subscription)
|
|
163
|
+
unless subscription.is_a? FHIR::Subscription
|
|
164
|
+
return operation_outcome('error', 'invalid', 'No recognized R4 Subscription in request body')
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
unless subscription.channel&.type == 'rest-hook'
|
|
168
|
+
return operation_outcome('error', 'business-rule', 'channel.type must be rest-hook')
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
unless valid_channel_endpoint?(subscription.channel&.endpoint)
|
|
172
|
+
return operation_outcome('error', 'value', 'channel.endpoint is not recognized as a conformant URL')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
heartbeat_period = find_heartbeat_period(subscription)
|
|
176
|
+
operation_outcome('error', 'not-supported', 'heartbeatPeriod is not supported') unless heartbeat_period.nil?
|
|
177
|
+
end
|
|
178
|
+
|
|
109
179
|
private
|
|
110
180
|
|
|
181
|
+
def parse_subscription(subscription_json, as_json: false)
|
|
182
|
+
if as_json
|
|
183
|
+
# needed to access primitive extensions
|
|
184
|
+
JSON.parse(subscription_json)
|
|
185
|
+
else
|
|
186
|
+
FHIR.from_contents(subscription_json)
|
|
187
|
+
end
|
|
188
|
+
rescue StandardError
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def find_heartbeat_period(subscription)
|
|
193
|
+
subscription.channel&.extension&.find do |e|
|
|
194
|
+
e.url == 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-heartbeat-period'
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def valid_channel_endpoint?(url)
|
|
199
|
+
uri = URI.parse(url)
|
|
200
|
+
%w[http https].include?(uri.scheme)
|
|
201
|
+
rescue URI::InvalidURIError
|
|
202
|
+
false
|
|
203
|
+
end
|
|
204
|
+
|
|
111
205
|
def requests_repo
|
|
112
206
|
@requests_repo ||= Inferno::Repositories::Requests.new
|
|
113
207
|
end
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"format": [
|
|
10
10
|
"json"
|
|
11
11
|
],
|
|
12
|
+
"patchFormat": [
|
|
13
|
+
"application/json-patch+json"
|
|
14
|
+
],
|
|
12
15
|
"software": {
|
|
13
16
|
"name": "Inferno Subscriptions Test Kit Client Suite"
|
|
14
17
|
},
|
|
@@ -42,6 +45,15 @@
|
|
|
42
45
|
},
|
|
43
46
|
{
|
|
44
47
|
"code": "create"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"code": "update"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"code": "patch"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"code": "delete"
|
|
45
57
|
}
|
|
46
58
|
],
|
|
47
59
|
"operation": [
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../common/subscription_simulation_utils'
|
|
4
|
+
|
|
5
|
+
module SubscriptionsTestKit
|
|
6
|
+
module SubscriptionsR5BackportR4Client
|
|
7
|
+
class DeleteVerificationTest < Inferno::Test
|
|
8
|
+
include SubscriptionSimulationUtils
|
|
9
|
+
|
|
10
|
+
id :subscriptions_r4_client_delete_verification
|
|
11
|
+
title 'Client Subscription Delete Conformance Verification'
|
|
12
|
+
description %(
|
|
13
|
+
This test verifies that the client under test sent a conformant
|
|
14
|
+
[delete](https://hl7.org/fhir/R4/http.html#delete) request for the Subscription it created
|
|
15
|
+
in this session to Inferno's simulated FHIR server.
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
run do
|
|
19
|
+
subscription = find_subscription(test_session_id)
|
|
20
|
+
skip_if(subscription.nil?, 'No Subscription was created in this session')
|
|
21
|
+
|
|
22
|
+
load_tagged_requests(SUBSCRIPTION_DELETE_TAG)
|
|
23
|
+
skip_if(requests.none?, 'Inferno did not receive a Subscription delete request')
|
|
24
|
+
|
|
25
|
+
delete_request = requests.find { |r| r.tags.include?(subscription.id) }
|
|
26
|
+
assert(delete_request.present?, %(
|
|
27
|
+
Subscription delete request(s) did not target the created Subscription with id #{subscription.id}.
|
|
28
|
+
Requested URL(s): #{requests.map { |r| "`#{r.url}`" }.join(', ')}
|
|
29
|
+
))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../../common/subscription_conformance_verification'
|
|
4
|
+
require_relative 'update_request_loading'
|
|
5
|
+
|
|
6
|
+
module SubscriptionsTestKit
|
|
7
|
+
module SubscriptionsR5BackportR4Client
|
|
8
|
+
class PatchUpdateVerificationTest < Inferno::Test
|
|
9
|
+
include SubscriptionConformanceVerification
|
|
10
|
+
include UpdateRequestLoading
|
|
11
|
+
|
|
12
|
+
id :subscriptions_r4_client_patch_update_verification
|
|
13
|
+
title 'Client Subscription PATCH Update Conformance Verification'
|
|
14
|
+
description %(
|
|
15
|
+
This test verifies that the [patch](https://hl7.org/fhir/R4/http.html#patch) (`PATCH`) requests
|
|
16
|
+
that the client under test sent to Inferno for the Subscription it created during this session
|
|
17
|
+
are conformant. Specifically, that each request
|
|
18
|
+
- was a [JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) that Inferno was able to
|
|
19
|
+
apply to the Subscription, and
|
|
20
|
+
- resulted in a patched Subscription that is conformant to the [R4/B Topic-Based Subscription
|
|
21
|
+
profile](https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/StructureDefinition-backport-subscription.html)
|
|
22
|
+
and meets other requirements placed on it by the IG.
|
|
23
|
+
|
|
24
|
+
Requests sent for other Subscription ids are not considered.
|
|
25
|
+
|
|
26
|
+
Inferno manages the `Subscription.status` and `Subscription.channel.payload` values itself (see the
|
|
27
|
+
*Subscription Update and Delete Interaction* test), so the values that the patch gives those elements
|
|
28
|
+
are not verified and not used by Inferno.
|
|
29
|
+
|
|
30
|
+
Inferno does not support XML Patch or FHIRPath Patch requests.
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
run do
|
|
34
|
+
load_update_requests('patch')
|
|
35
|
+
|
|
36
|
+
verify_update_requests do |patch_request|
|
|
37
|
+
# The response contains the Subscription with the patch applied, except for the status and
|
|
38
|
+
# payload values that Inferno manages
|
|
39
|
+
subscription_verification(patch_request.response_body)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../../common/subscription_conformance_verification'
|
|
4
|
+
require_relative 'update_request_loading'
|
|
5
|
+
|
|
6
|
+
module SubscriptionsTestKit
|
|
7
|
+
module SubscriptionsR5BackportR4Client
|
|
8
|
+
class PutUpdateVerificationTest < Inferno::Test
|
|
9
|
+
include SubscriptionConformanceVerification
|
|
10
|
+
include UpdateRequestLoading
|
|
11
|
+
|
|
12
|
+
id :subscriptions_r4_client_put_update_verification
|
|
13
|
+
title 'Client Subscription PUT Update Conformance Verification'
|
|
14
|
+
description %(
|
|
15
|
+
This test verifies that the [update](https://hl7.org/fhir/R4/http.html#update) (`PUT`) requests
|
|
16
|
+
that the client under test sent to Inferno for the Subscription it created during this session
|
|
17
|
+
are conformant. Specifically, that each request
|
|
18
|
+
- was accepted by Inferno, which requires, for example, that the `id` of the Subscription in the request
|
|
19
|
+
body matches the `id` in the request URL, and
|
|
20
|
+
- contained a Subscription in the request body that is conformant to the [R4/B Topic-Based Subscription
|
|
21
|
+
profile](https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/StructureDefinition-backport-subscription.html)
|
|
22
|
+
and meets other requirements placed on it by the IG.
|
|
23
|
+
|
|
24
|
+
Requests sent for other Subscription ids are not considered.
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
run do
|
|
28
|
+
load_update_requests('put')
|
|
29
|
+
|
|
30
|
+
verify_update_requests do |update_request|
|
|
31
|
+
subscription_verification(update_request.request_body)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../common/subscription_simulation_utils'
|
|
4
|
+
|
|
5
|
+
module SubscriptionsTestKit
|
|
6
|
+
module SubscriptionsR5BackportR4Client
|
|
7
|
+
module UpdateRequestLoading
|
|
8
|
+
include SubscriptionSimulationUtils
|
|
9
|
+
|
|
10
|
+
# Loads the update requests for the created Subscription that used the given HTTP method. Requests that
|
|
11
|
+
# targeted other ids are ignored. Skips if the client did not send any such requests.
|
|
12
|
+
def load_update_requests(verb)
|
|
13
|
+
subscription = find_subscription(test_session_id)
|
|
14
|
+
skip_if(subscription.nil?, 'No Subscription was created in this session')
|
|
15
|
+
|
|
16
|
+
update_requests = requests_repo.tagged_requests(test_session_id, [SUBSCRIPTION_UPDATE_TAG, subscription.id])
|
|
17
|
+
requests.concat(update_requests.select { |r| r.verb.casecmp?(verb) })
|
|
18
|
+
skip_if(requests.none?, %(
|
|
19
|
+
Inferno did not receive a #{verb.upcase} request for the created Subscription with id #{subscription.id}
|
|
20
|
+
))
|
|
21
|
+
|
|
22
|
+
subscription
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Verifies each loaded update request, using the given block for the checks specific to the HTTP method.
|
|
26
|
+
# Every request must have been accepted by Inferno and pass those checks. Each message is prefixed with
|
|
27
|
+
# the number of the request that it is about.
|
|
28
|
+
def verify_update_requests(&)
|
|
29
|
+
requests.each.with_index(1) do |update_request, request_number|
|
|
30
|
+
verify_update_request(update_request, request_number, &)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
no_error_verification("Subscription #{requests.first.verb.upcase} update request is not conformant.")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def verify_update_request(update_request, request_number)
|
|
37
|
+
first_message_index = messages.length
|
|
38
|
+
begin
|
|
39
|
+
assert(update_request.status == 200, %(
|
|
40
|
+
Inferno could not apply the request (status #{update_request.status}):
|
|
41
|
+
#{error_response_text(update_request.response_body)}
|
|
42
|
+
))
|
|
43
|
+
yield update_request
|
|
44
|
+
rescue Inferno::Exceptions::AssertionException => e
|
|
45
|
+
add_message('error', e.message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
messages[first_message_index..].each do |message|
|
|
49
|
+
message[:message] = "Request #{request_number}: #{message[:message]}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def error_response_text(response_body)
|
|
54
|
+
outcome = JSON.parse(response_body)
|
|
55
|
+
(outcome.dig('issue', 0, 'details', 'text') if outcome.is_a?(Hash)) || 'no details provided'
|
|
56
|
+
rescue JSON::ParserError, TypeError
|
|
57
|
+
'no details provided'
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'conformance_verification/put_update_verification_test'
|
|
4
|
+
require_relative 'conformance_verification/patch_update_verification_test'
|
|
5
|
+
require_relative 'conformance_verification/delete_verification_test'
|
|
6
|
+
|
|
7
|
+
module SubscriptionsTestKit
|
|
8
|
+
module SubscriptionsR5BackportR4Client
|
|
9
|
+
class UpdateDeleteConformanceVerificationGroup < Inferno::TestGroup
|
|
10
|
+
id :subscriptions_r4_client_update_delete_conformance_verification
|
|
11
|
+
title 'Subscription Update and Delete Conformance Verification'
|
|
12
|
+
|
|
13
|
+
test from: :subscriptions_r4_client_put_update_verification, optional: true
|
|
14
|
+
test from: :subscriptions_r4_client_patch_update_verification, optional: true
|
|
15
|
+
test from: :subscriptions_r4_client_delete_verification, optional: true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../common/subscription_simulation_utils'
|
|
4
|
+
|
|
5
|
+
module SubscriptionsTestKit
|
|
6
|
+
module SubscriptionsR5BackportR4Client
|
|
7
|
+
class UpdateDeleteInteractionTest < Inferno::Test
|
|
8
|
+
include URLs
|
|
9
|
+
include SubscriptionSimulationUtils
|
|
10
|
+
|
|
11
|
+
id :subscriptions_r4_client_update_delete_interaction
|
|
12
|
+
title 'Subscription Update and Delete Interaction'
|
|
13
|
+
description %(
|
|
14
|
+
During this test, Inferno will wait while the client system changes the Subscription
|
|
15
|
+
that it created during the *Demonstrate the Rest-Hook Subscription Workflow* tests.
|
|
16
|
+
Inferno accepts the FHIR [update](https://hl7.org/fhir/R4/http.html#update),
|
|
17
|
+
[patch](https://hl7.org/fhir/R4/http.html#patch) (JSON only), and [delete](https://hl7.org/fhir/R4/http.html#delete)
|
|
18
|
+
interactions and testers can demonstrate system support for any or all of them.
|
|
19
|
+
|
|
20
|
+
Additional notes:
|
|
21
|
+
- Inferno will not send additional notifications or handshakes based on the updated Subscription.
|
|
22
|
+
- Inferno will modify the `Subscription.channel.payload` value on the updated Subscription to
|
|
23
|
+
`application/fhir+json` unless it is `application/json`.
|
|
24
|
+
- Inferno manages the `Subscription.status` value and will ignore requested changes to it, unless
|
|
25
|
+
the requested status is `off`.
|
|
26
|
+
- Inferno only accepts updates and deletes of the Subscription while this test is waiting.
|
|
27
|
+
- Each run of this test starts from the Subscription as created during the *Demonstrate the Rest-Hook
|
|
28
|
+
Subscription Workflow* tests. Changes made during earlier runs of this test, including deletion,
|
|
29
|
+
are not carried over.
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
input :access_token,
|
|
33
|
+
title: 'Access Token',
|
|
34
|
+
description: %(
|
|
35
|
+
The bearer token that the client under test will use when making Subscription update, delete, and other
|
|
36
|
+
requests to Inferno's simulated Subscriptions server.
|
|
37
|
+
)
|
|
38
|
+
# Inferno's Subscription update and delete endpoints only accept requests while this test is waiting
|
|
39
|
+
config options: { accepts_subscription_changes: true }
|
|
40
|
+
|
|
41
|
+
output :confirmation_url
|
|
42
|
+
output :fail_url
|
|
43
|
+
output :subscription_url
|
|
44
|
+
|
|
45
|
+
run do
|
|
46
|
+
subscription = find_subscription(test_session_id)
|
|
47
|
+
skip_if(subscription.nil?, %(
|
|
48
|
+
No Subscription was created in this session. Run the *Demonstrate the Rest-Hook Subscription Workflow*
|
|
49
|
+
tests before running these tests.
|
|
50
|
+
))
|
|
51
|
+
|
|
52
|
+
subscription_url = "#{fhir_subscription_url}/#{subscription.id}"
|
|
53
|
+
output(subscription_url:)
|
|
54
|
+
confirmation_url = "#{resume_pass_url_client}?test_run_identifier=#{access_token}"
|
|
55
|
+
output(confirmation_url:)
|
|
56
|
+
fail_url = "#{resume_fail_url_client}?test_run_identifier=#{access_token}"
|
|
57
|
+
output(fail_url:)
|
|
58
|
+
|
|
59
|
+
wait(
|
|
60
|
+
identifier: access_token,
|
|
61
|
+
timeout: 600,
|
|
62
|
+
message: <<~MESSAGE
|
|
63
|
+
Inferno will wait while the client updates and/or deletes the Subscription created earlier
|
|
64
|
+
in the session. The Subscription is in the state it was created in: changes made during
|
|
65
|
+
earlier runs of these tests, including deletion, have not been kept. Inferno accepts the FHIR [update](https://hl7.org/fhir/R4/http.html#update),
|
|
66
|
+
[patch](https://hl7.org/fhir/R4/http.html#patch) (JSON only), and [delete](https://hl7.org/fhir/R4/http.html#delete)
|
|
67
|
+
interactions and testers can demonstrate system support for any or all of them by making
|
|
68
|
+
a request using the corresponding HTTP verb against `#{subscription_url}`:
|
|
69
|
+
|
|
70
|
+
- **PUT**: Change the Subscription with the update interaction by sending
|
|
71
|
+
a PUT request containing an updated Subscription resource in the request body.
|
|
72
|
+
- **PATCH**: Change the Subscription with a patch interaction by sending
|
|
73
|
+
a PATCH request containing a JSON Patch (`Content-Type: application/json-patch+json`)
|
|
74
|
+
in the request body
|
|
75
|
+
- **DELETE**: Remove the Subscription with a delete interaction by sending a
|
|
76
|
+
DELETE request with no body.
|
|
77
|
+
|
|
78
|
+
All requests must have the Authorization header set to:
|
|
79
|
+
|
|
80
|
+
`Bearer #{access_token}`
|
|
81
|
+
|
|
82
|
+
At any point while this test is waiting, Inferno will respond to Subscription GET and $status requests
|
|
83
|
+
for the Subscription until it has been deleted.
|
|
84
|
+
|
|
85
|
+
Once the client has demonstrated the Subscription interactions it supports,
|
|
86
|
+
[click here to complete the test](#{confirmation_url})
|
|
87
|
+
|
|
88
|
+
If at any point something has gone wrong and the client is unable to continue,
|
|
89
|
+
[click here to fail the test](#{fail_url})
|
|
90
|
+
MESSAGE
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'update_delete_workflow/update_delete_interaction_test'
|
|
4
|
+
require_relative 'update_delete_workflow/update_delete_conformance_verification_group'
|
|
5
|
+
|
|
6
|
+
module SubscriptionsTestKit
|
|
7
|
+
module SubscriptionsR5BackportR4Client
|
|
8
|
+
class UpdateDeleteWorkflowGroup < Inferno::TestGroup
|
|
9
|
+
id :subscriptions_r4_client_update_delete_workflow
|
|
10
|
+
title 'Demonstrate Subscription Update and Deletion'
|
|
11
|
+
description %(
|
|
12
|
+
During this test, the tester will demonstrate the ability of the client
|
|
13
|
+
to update and/or delete the Subscription instance that it created
|
|
14
|
+
during the *Demonstrate the Rest-Hook Subscription Workflow* tests.
|
|
15
|
+
Those tests must be run first in the same session.
|
|
16
|
+
|
|
17
|
+
Inferno will wait for the client to send update and delete requests and
|
|
18
|
+
will then verify that the requests are conformant. Note that
|
|
19
|
+
- Update and delete requests are only accepted by Inferno while
|
|
20
|
+
this group is waiting.
|
|
21
|
+
- If this group is re-run, Inferno will forget about the previous
|
|
22
|
+
update and delete interactions sent by the Subscriptions client.
|
|
23
|
+
The Subscriptions client can interact with it as it was after
|
|
24
|
+
it was created. If the Subscriptions client cannot reactivate
|
|
25
|
+
the deleted Subscription and make updates to it again, re-run
|
|
26
|
+
the *Demonstrate the Rest-Hook Subscription Workflow* group
|
|
27
|
+
to create a new Subscription.
|
|
28
|
+
)
|
|
29
|
+
run_as_group
|
|
30
|
+
optional
|
|
31
|
+
|
|
32
|
+
group 'Subscription Update and Delete Interaction' do
|
|
33
|
+
id :subscriptions_r4_client_update_delete_workflow_interaction
|
|
34
|
+
test from: :subscriptions_r4_client_update_delete_interaction
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
group from: :subscriptions_r4_client_update_delete_conformance_verification
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -14,13 +14,15 @@ module SubscriptionsTestKit
|
|
|
14
14
|
to the [R4 Topic-Based Subscription Notification Bundle
|
|
15
15
|
profile](https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/StructureDefinition-backport-subscription-notification-r4.html).
|
|
16
16
|
The content of the notification will be based on the Bundle provided by the tester.
|
|
17
|
+
To ensure that the subscriptions client is driving its behavior only based on
|
|
18
|
+
standard information, custom extensions are not allowed within the notifcation Bundle.
|
|
17
19
|
)
|
|
18
20
|
simulation_verification
|
|
19
21
|
|
|
20
22
|
run do
|
|
21
23
|
load_tagged_requests(REST_HOOK_EVENT_NOTIFICATION_TAG)
|
|
22
24
|
skip_if(requests.none?, 'Inferno did not send an event notification')
|
|
23
|
-
notification_verification(request.request_body, 'event-notification')
|
|
25
|
+
notification_verification(request.request_body, 'event-notification', validator: :no_custom_extensions)
|
|
24
26
|
no_error_verification('Notification bundle was not conformant, see error messages')
|
|
25
27
|
end
|
|
26
28
|
end
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'subscriptions_r5_backport_r4_client/workflow_group'
|
|
4
|
+
require_relative 'subscriptions_r5_backport_r4_client/update_delete_workflow_group'
|
|
5
|
+
require_relative '../fhir_validation'
|
|
4
6
|
require_relative '../endpoints/subscription_create_endpoint'
|
|
5
7
|
require_relative '../endpoints/subscription_read_endpoint'
|
|
8
|
+
require_relative '../endpoints/subscription_update_endpoint'
|
|
9
|
+
require_relative '../endpoints/subscription_patch_endpoint'
|
|
10
|
+
require_relative '../endpoints/subscription_delete_endpoint'
|
|
6
11
|
require_relative '../endpoints/subscription_status_endpoint'
|
|
7
12
|
|
|
8
13
|
module SubscriptionsTestKit
|
|
@@ -43,10 +48,23 @@ module SubscriptionsTestKit
|
|
|
43
48
|
|
|
44
49
|
# All FHIR validation requests will use this FHIR validator
|
|
45
50
|
fhir_resource_validator do
|
|
46
|
-
igs
|
|
51
|
+
igs SUBSCRIPTIONS_BACKPORT_IG
|
|
47
52
|
|
|
48
53
|
exclude_message do |message|
|
|
49
|
-
message.message.match?(
|
|
54
|
+
message.message.match?(UNRESOLVED_URL_VALIDATION_MESSAGE)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Used to validate tester-provided content so that it cannot rely on extensions not defined in the IG
|
|
59
|
+
fhir_resource_validator :no_custom_extensions do
|
|
60
|
+
igs SUBSCRIPTIONS_BACKPORT_IG
|
|
61
|
+
|
|
62
|
+
validation_context do
|
|
63
|
+
extensions [] # no extensions not in the spec
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
exclude_message do |message|
|
|
67
|
+
message.message.match?(UNRESOLVED_URL_VALIDATION_MESSAGE)
|
|
50
68
|
end
|
|
51
69
|
end
|
|
52
70
|
|
|
@@ -61,6 +79,9 @@ module SubscriptionsTestKit
|
|
|
61
79
|
|
|
62
80
|
suite_endpoint :post, FHIR_SUBSCRIPTION_PATH, SubscriptionCreateEndpoint
|
|
63
81
|
suite_endpoint :get, FHIR_SUBSCRIPTION_INSTANCE_PATH, SubscriptionReadEndpoint
|
|
82
|
+
suite_endpoint :put, FHIR_SUBSCRIPTION_INSTANCE_PATH, SubscriptionUpdateEndpoint
|
|
83
|
+
suite_endpoint :patch, FHIR_SUBSCRIPTION_INSTANCE_PATH, SubscriptionPatchEndpoint
|
|
84
|
+
suite_endpoint :delete, FHIR_SUBSCRIPTION_INSTANCE_PATH, SubscriptionDeleteEndpoint
|
|
64
85
|
suite_endpoint :post, FHIR_SUBSCRIPTION_INSTANCE_STATUS_PATH, SubscriptionStatusEndpoint
|
|
65
86
|
suite_endpoint :get, FHIR_SUBSCRIPTION_INSTANCE_STATUS_PATH, SubscriptionStatusEndpoint
|
|
66
87
|
suite_endpoint :post, FHIR_SUBSCRIPTION_RESOURCE_STATUS_PATH, SubscriptionStatusEndpoint
|
|
@@ -74,6 +95,7 @@ module SubscriptionsTestKit
|
|
|
74
95
|
end
|
|
75
96
|
|
|
76
97
|
group from: :subscriptions_r4_client_workflow
|
|
98
|
+
group from: :subscriptions_r4_client_update_delete_workflow
|
|
77
99
|
end
|
|
78
100
|
end
|
|
79
101
|
end
|
|
@@ -11,7 +11,8 @@ module SubscriptionsTestKit
|
|
|
11
11
|
This test accepts a Subscription resource as an input and verifies that it is conformant to the
|
|
12
12
|
[R4/B Topic-Based Subscription profile](https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/StructureDefinition-backport-subscription.html)
|
|
13
13
|
and that it is constructed to make supported notifications to Inferno's simulated
|
|
14
|
-
Subscriptions client.
|
|
14
|
+
Subscriptions client. To ensure that the subscriptions server is driving its behavior only based on
|
|
15
|
+
standard information, custom extensions are not allowed within the provided Subscription.
|
|
15
16
|
|
|
16
17
|
For the server to successfuly deliver notifications recognized by this test session, the Subscription `channel`
|
|
17
18
|
element should have it's subelements populated with the following information:
|
|
@@ -65,7 +66,7 @@ module SubscriptionsTestKit
|
|
|
65
66
|
updated_subscription = subscription.to_json
|
|
66
67
|
output(updated_subscription:)
|
|
67
68
|
|
|
68
|
-
subscription_verification(updated_subscription)
|
|
69
|
+
subscription_verification(updated_subscription, validator: :no_custom_extensions)
|
|
69
70
|
no_error_verification('Subscription resource is not conformant.')
|
|
70
71
|
end
|
|
71
72
|
end
|
|
@@ -63,6 +63,7 @@ module SubscriptionsTestKit
|
|
|
63
63
|
tags = ['subscription_creation']
|
|
64
64
|
payload_type = subscription_payload_type(subscription)
|
|
65
65
|
tags.append(payload_type) if payload_type.present?
|
|
66
|
+
tags.append('workflow') if config.options[:workflow_subscription]
|
|
66
67
|
|
|
67
68
|
fhir_operation('/Subscription', body: subscription, tags:)
|
|
68
69
|
assert_response_status(201)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module SubscriptionsTestKit
|
|
2
|
+
module SubscriptionsR5BackportR4Server
|
|
3
|
+
module SubscriptionUpdateDelete
|
|
4
|
+
def created_subscription_id
|
|
5
|
+
creation_requests = load_tagged_requests('subscription_creation', 'workflow').select { |r| r.status == 201 }
|
|
6
|
+
skip_if creation_requests.empty?, %(
|
|
7
|
+
No successful Subscription creation request has been made during the "Demonstrate the Subscription Workflow"
|
|
8
|
+
group. Run that group successfully before re-running this one.
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
JSON.parse(creation_requests.first.response_body)['id']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns the raw JSON hash so that primitive extensions, such as the payload content extension on
|
|
15
|
+
# Subscription.channel.payload, are preserved
|
|
16
|
+
def read_subscription_json(subscription_id)
|
|
17
|
+
fhir_read(:subscription, subscription_id)
|
|
18
|
+
assert_response_status(200)
|
|
19
|
+
assert_valid_json(request.response_body)
|
|
20
|
+
JSON.parse(request.response_body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# FHIR models drop primitive extensions, so send the raw JSON as the body of the update
|
|
24
|
+
def raw_subscription_resource(subscription_hash)
|
|
25
|
+
FHIR::Subscription.new(subscription_hash).tap do |resource|
|
|
26
|
+
resource.define_singleton_method(:to_json) { |*| subscription_hash.to_json }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def updated_reason(subscription, interaction)
|
|
31
|
+
"#{subscription['reason']} (updated by Inferno using #{interaction} at #{Time.now.utc.iso8601})"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def verify_reason_updated(subscription_id, expected_reason)
|
|
35
|
+
subscription = read_subscription_json(subscription_id)
|
|
36
|
+
assert(subscription['reason'] == expected_reason, %(
|
|
37
|
+
Expected the `reason` of the updated Subscription to be `#{expected_reason}`, but was
|
|
38
|
+
`#{subscription['reason']}`.
|
|
39
|
+
))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|