subscriptions_test_kit 0.12.1 → 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 +40 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22bedb9e7ab1b3784b17117262e36654dd9bd9d652a2ae81db58abfcd6ca8b04
|
|
4
|
+
data.tar.gz: 72a233c69e0554ef66d8031504f3cbb962cef548e8507ecc95dbdd18ee94998b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6161dbfb63aa5950300c2d3a092acef3e42dc43c9e976b11d1491da3eaaa937fcb0cafa553ac0b0f76f8d906c228f2874b0bbb4cc3bc6309d3f12df92b5c6925
|
|
7
|
+
data.tar.gz: 0f5ced8f4e1ebf1d856d391cda49f1e5a50f90bd2e6c36e621ced9f839043ca78f8c55b05bd71533271f132937f9e9ac0ebfe3aae6a3f32f08082aedeb660de1
|
|
@@ -48,17 +48,19 @@ module SubscriptionsTestKit
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def parameters_verification(subscription_status)
|
|
51
|
+
def parameters_verification(subscription_status, validator: :default)
|
|
52
52
|
resource_type = subscription_status.resourceType
|
|
53
53
|
if resource_type == 'Parameters'
|
|
54
|
-
resource_is_valid?(resource: subscription_status,
|
|
54
|
+
resource_is_valid?(resource: subscription_status, validator:,
|
|
55
|
+
profile_url: 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-subscription-status-r4')
|
|
55
56
|
else
|
|
56
57
|
add_message('error',
|
|
57
58
|
"Unexpected resource type: Expected `Parameters`. Got `#{resource_type}`")
|
|
58
59
|
end
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
def notification_verification(notification_bundle, notification_type, subscription_id: nil, status: nil
|
|
62
|
+
def notification_verification(notification_bundle, notification_type, subscription_id: nil, status: nil,
|
|
63
|
+
validator: :default)
|
|
62
64
|
assert_valid_json(notification_bundle)
|
|
63
65
|
bundle = FHIR.from_contents(notification_bundle)
|
|
64
66
|
assert bundle.present?, 'Not a FHIR resource'
|
|
@@ -76,7 +78,7 @@ module SubscriptionsTestKit
|
|
|
76
78
|
check_history_bundle_request_response(bundle, subscription_status_entry, subscription_id)
|
|
77
79
|
|
|
78
80
|
subscription_status = subscription_status_entry.resource
|
|
79
|
-
parameters_verification(subscription_status)
|
|
81
|
+
parameters_verification(subscription_status, validator:)
|
|
80
82
|
|
|
81
83
|
return unless subscription_status.respond_to?(:parameter)
|
|
82
84
|
|
|
@@ -44,11 +44,13 @@ module SubscriptionsTestKit
|
|
|
44
44
|
false
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def subscription_verification(subscription_resource)
|
|
47
|
+
def subscription_verification(subscription_resource, validator: :default)
|
|
48
48
|
assert_valid_json(subscription_resource)
|
|
49
49
|
subscription = JSON.parse(subscription_resource)
|
|
50
|
+
assert(subscription.is_a?(Hash), 'The Subscription must be a JSON object.')
|
|
50
51
|
|
|
51
52
|
subscription_channel = subscription['channel']
|
|
53
|
+
assert(subscription_channel.is_a?(Hash), 'The Subscription must include a `channel` element.')
|
|
52
54
|
assert(channel_field_matches?(subscription_channel, 'type', 'rest-hook'), %(
|
|
53
55
|
The `type` element on the Subscription resource must be set to `rest-hook`,
|
|
54
56
|
the `#{subscription_channel['type']}` channel type is unsupported.))
|
|
@@ -62,7 +64,7 @@ module SubscriptionsTestKit
|
|
|
62
64
|
end
|
|
63
65
|
subscription_resource = FHIR.from_contents(subscription.to_json)
|
|
64
66
|
assert_resource_type('Subscription', resource: subscription_resource)
|
|
65
|
-
assert_valid_resource(resource: subscription_resource,
|
|
67
|
+
assert_valid_resource(resource: subscription_resource, validator:,
|
|
66
68
|
profile_url: 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-subscription')
|
|
67
69
|
|
|
68
70
|
cross_version_extension_check(subscription)
|
data/lib/subscriptions_test_kit/docs/subscriptions_r5_backport_r4_client_suite_description.md
CHANGED
|
@@ -13,20 +13,26 @@ requirements and may change the test verification logic.
|
|
|
13
13
|
|
|
14
14
|
## Test Methodology
|
|
15
15
|
|
|
16
|
-
For these tests Inferno simulates a Subscriptions server application.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
For these tests Inferno simulates a Subscriptions server application. The client contains
|
|
17
|
+
two groups:
|
|
18
|
+
- *Demonstrate the Rest-Hook Subscription Workflow*, which tests the client's ability to
|
|
19
|
+
create a Subscription using the rest-hook channel and receive notifications based on it.
|
|
20
|
+
- *Demonstrate Subscription Update and Deletion*, which tests the client's ability to
|
|
21
|
+
update and delete the Subscription created during the first group.
|
|
22
|
+
|
|
23
|
+
During each group, Inferno will wait for the client system to make requests against Inferno.
|
|
24
|
+
Inferno will react to those requests, triggering handshakes and notifications.
|
|
25
|
+
Using the interactions between Inferno and the client system, Inferno will verify that
|
|
20
26
|
the client meets the obligations from the implementation guide both on individual interactions
|
|
21
27
|
and in aggregate.
|
|
22
28
|
|
|
23
|
-
Inferno does not implement a real data repository that recognizes resource-level changes
|
|
24
|
-
|
|
25
|
-
to the client
|
|
26
|
-
notifications, such as handshakes and `$status` operation
|
|
27
|
-
and respond with as directed by the IG. Inferno will verify that the
|
|
28
|
-
notification is conformant and that it matches the requirements for the
|
|
29
|
-
sent by the client
|
|
29
|
+
Inferno does not implement a real data repository that recognizes resource-level changes.
|
|
30
|
+
So, for the first group, Inferno relies on the tester to provide an event notification that
|
|
31
|
+
Inferno will send back to the client system. Inferno will manipulate this provided
|
|
32
|
+
notification to generate other notifications, such as handshakes and `$status` operation
|
|
33
|
+
responses, to send and respond with as directed by the IG. Inferno will verify that the
|
|
34
|
+
provided event notification is conformant and that it matches the requirements for the
|
|
35
|
+
Subscription sent by the client system.
|
|
30
36
|
|
|
31
37
|
The content of all Subscription interactions sent by the client,
|
|
32
38
|
as well as tester-provided notifications, will be checked
|
|
@@ -41,7 +47,7 @@ HL7® FHIR® resources are checked for conformance using the Java validator with
|
|
|
41
47
|
|
|
42
48
|
The following inputs must be provided by the tester at a minimum to execute
|
|
43
49
|
any tests in this suite:
|
|
44
|
-
1. *Access Token*: A `Bearer` token that the client
|
|
50
|
+
1. *Access Token*: A `Bearer` token that the client system will send in the
|
|
45
51
|
`Authorization` header of HTTP requests made against Inferno. Inferno uses the
|
|
46
52
|
value to identify incoming requests that belong to the testing session.
|
|
47
53
|
1. *Notification Bundle*: an `event-notification` conforming to the [R4 Topic-Based
|
|
@@ -49,7 +55,7 @@ any tests in this suite:
|
|
|
49
55
|
Inferno uses this as the basis for responses and notifications that it will send
|
|
50
56
|
to the client.
|
|
51
57
|
|
|
52
|
-
All other details needed to interact with the client
|
|
58
|
+
All other details needed to interact with the client system, such as where to send notifications,
|
|
53
59
|
will be determined from the subscription that the client will create. Once the testing start,
|
|
54
60
|
Inferno will wait for a Subscription creation request and then send the relevant handshakes
|
|
55
61
|
and notification requests to the client and verify the interaction.
|
|
@@ -81,8 +87,14 @@ To run the client tests against the server tests:
|
|
|
81
87
|
1. Wait 10-15 seconds while the client session makes requests,
|
|
82
88
|
then click the "Click here" link in the wait dialog to evaluate the notifications.
|
|
83
89
|
1. Back in the client session, click the "click here to complete the test" link
|
|
84
|
-
and respond to the attestation based on the results in the server session
|
|
85
|
-
|
|
90
|
+
and respond to the attestation based on the results in the server session.
|
|
91
|
+
1. The client session will then wait for the Subscription to be updated and deleted. The server
|
|
92
|
+
session's *Subscription Update and Deletion Verification* tests have already made these requests,
|
|
93
|
+
but they will have failed as Inferno only accepts these interactions when made during this test.
|
|
94
|
+
Return to the server session and re-run the *Subscription Update and Deletion Verification*
|
|
95
|
+
group.
|
|
96
|
+
1. Once the server run has completed, return to the client session and click the "click here to
|
|
97
|
+
complete the test" link and review the results.
|
|
86
98
|
|
|
87
99
|
NOTE: Inferno uses the `Bearer` token sent in the `Authorization` HTTP header
|
|
88
100
|
to associate requests with sessions. If multiple concurrent sessions are configured
|
|
@@ -99,7 +111,7 @@ that appears when initiating a test run.
|
|
|
99
111
|
|
|
100
112
|
### Client Notification Access Token
|
|
101
113
|
|
|
102
|
-
If the client
|
|
114
|
+
If the client system needs Inferno to send a specific `Bearer` token in the HTTP `Authorization` header
|
|
103
115
|
when sending notifications and it cannot be specified in the `channel.header` element of the Subscription
|
|
104
116
|
instance that the client sends to Inferno, then a value can be provided here.
|
|
105
117
|
|
|
@@ -111,7 +123,7 @@ that the test kit covers and does not cover in the [Requirements
|
|
|
111
123
|
Coverage](https://github.com/inferno-framework/subscriptions-test-kit/blob/main/lib/subscriptions_test_kit/requirements/generated/subscriptions-test-kit_requirements_coverage.csv)
|
|
112
124
|
CSV document.
|
|
113
125
|
|
|
114
|
-
Specific limitations to highlight include
|
|
126
|
+
Specific limitations to highlight include:
|
|
115
127
|
- Inferno supports only the `rest-hook` channel type. Support for other channels may be added in the future.
|
|
116
128
|
If there is a channel type that you would like to see verified, please
|
|
117
129
|
[provide feedback](https://github.com/inferno-framework/subscriptions-test-kit/issues) to that effect.
|
|
@@ -123,4 +135,13 @@ Specific limitations to highlight include
|
|
|
123
135
|
are defined within the SubscriptionTopic which is not available in FHIR R4.
|
|
124
136
|
- When sending notifications, Inferno supports only JSON payloads and will always use `application/fhir+json`
|
|
125
137
|
as the value of the `content-type` HTTP header, unless the client system explicitly asks for
|
|
126
|
-
`application/json` using the `Subscription.channel.payload` element.
|
|
138
|
+
`application/json` using the `Subscription.channel.payload` element.
|
|
139
|
+
- Inferno supports Subscription `PATCH` requests only when they use JSON Patch. XML Patch and FHIRPath Patch
|
|
140
|
+
are not supported.
|
|
141
|
+
- Inferno does not support re-creating a deleted Subscription with a `PUT` request. Once the client deletes the
|
|
142
|
+
Subscription, Inferno responds to `PUT` and `PATCH` requests for it with `404 Not Found`, and the update
|
|
143
|
+
conformance verification tests will report those requests as failures.
|
|
144
|
+
- Inferno records each request it receives just after responding to it, and its simulated Subscription reflects
|
|
145
|
+
only the requests it has recorded. As a result, a request sent immediately after an update or delete, for
|
|
146
|
+
example in parallel or over a separate connection, may be answered as if that change had not happened yet.
|
|
147
|
+
If Inferno's responses appear inconsistent with earlier changes, pause briefly between requests.
|
data/lib/subscriptions_test_kit/docs/subscriptions_r5_backport_r4_server_suite_description.md
CHANGED
|
@@ -24,13 +24,17 @@ Over the course of a test session, Inferno may request multiple Subscriptions to
|
|
|
24
24
|
different features of the IG, e.g., different notification content types. Because the
|
|
25
25
|
choice of what topics and specific features to implement is left to individual servers,
|
|
26
26
|
Inferno does not dictate specific Subscription features or topics. Instead, the
|
|
27
|
-
tester will provide
|
|
27
|
+
tester will provide Subscription instances that Inferno will submit, modified lightly if needed,
|
|
28
28
|
e.g., to point to Inferno's notification endpoint, to the server
|
|
29
29
|
as a part of the tests. In order for the tests to pass, the provided Subscription instances
|
|
30
30
|
must themselves be conformant and consistent with the notifications subsequently sent,
|
|
31
31
|
but no other requirements are placed on them,
|
|
32
32
|
except those related to Inferno limitations (see the Current Limitations section below).
|
|
33
33
|
|
|
34
|
+
At the end of the session, Inferno will update the Subscription created during the Subscription
|
|
35
|
+
workflow tests using `PUT` and JSON Patch `PATCH` requests and then delete it, verifying that the
|
|
36
|
+
server supports these optional interactions.
|
|
37
|
+
|
|
34
38
|
The content of all interactions sent by the server (responses and notifications),
|
|
35
39
|
as well as tester-provided requests, will be checked
|
|
36
40
|
for conformance to the Subscriptions IG requirements individually and used in
|
|
@@ -91,8 +95,14 @@ To run the server tests against the client tests:
|
|
|
91
95
|
1. Wait 10-15 seconds while the client session makes requests,
|
|
92
96
|
then click the "Click here" link in the wait dialog to evaluate the notifications.
|
|
93
97
|
1. Back in the client session, click the "click here to complete the test" link
|
|
94
|
-
and respond to the attestation
|
|
95
|
-
1.
|
|
98
|
+
and respond to the attestation based on the results in the server session.
|
|
99
|
+
1. The client session will then wait for the Subscription to be updated and deleted. The server
|
|
100
|
+
session's *Subscription Update and Deletion Verification* tests have already made these requests,
|
|
101
|
+
but they will have failed as Inferno only accepts these interactions when made during this test.
|
|
102
|
+
Return to the server session and re-run the *Subscription Update and Deletion Verification*
|
|
103
|
+
group.
|
|
104
|
+
1. Once the server run has completed, return to the client session and click the "click here to
|
|
105
|
+
complete the test" link. Return to the server session to review the results.
|
|
96
106
|
|
|
97
107
|
NOTE: Inferno uses the `Bearer` token sent in the `Authorization` HTTP header
|
|
98
108
|
to associate requests with sessions. If multiple concurrent sessions are configured
|
|
@@ -177,4 +187,6 @@ Specific limitations to highlight include
|
|
|
177
187
|
header using the `Subscription.channel.payload` element. Testers may instruct
|
|
178
188
|
Inferno to request that `application/json` be used instead by placing it in the
|
|
179
189
|
`Subscription.channel.payload` element of the Subscription they provide for Inferno
|
|
180
|
-
to use.
|
|
190
|
+
to use.
|
|
191
|
+
- When verifying Subscription modification using `PATCH`, Inferno only uses JSON Patch. XML Patch and
|
|
192
|
+
FHIRPath Patch are not tested.
|
|
@@ -6,23 +6,25 @@ module SubscriptionsTestKit
|
|
|
6
6
|
class SubscriptionCreateEndpoint < Inferno::DSL::SuiteEndpoint
|
|
7
7
|
include SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
|
8
8
|
|
|
9
|
+
error_response_format :operation_outcome
|
|
10
|
+
|
|
9
11
|
def test_run_identifier
|
|
10
12
|
request.headers['authorization']&.delete_prefix('Bearer ')
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def make_response
|
|
14
16
|
response.format = :json
|
|
15
|
-
response.status = 400
|
|
16
17
|
|
|
17
18
|
begin
|
|
18
19
|
subscription = FHIR.from_contents(request.body.string)
|
|
19
20
|
rescue StandardError
|
|
20
|
-
|
|
21
|
+
fhir_error_response(400, 'invalid', 'No recognized R4 Subscription in request body')
|
|
21
22
|
return
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
verification_outcome = verify_subscription(subscription)
|
|
25
26
|
if verification_outcome.present?
|
|
27
|
+
response.status = 400
|
|
26
28
|
response.body = verification_outcome.to_json
|
|
27
29
|
return
|
|
28
30
|
end
|
|
@@ -34,7 +36,7 @@ module SubscriptionsTestKit
|
|
|
34
36
|
subscription_hash = JSON.parse(existing_subscription_request.response_body)
|
|
35
37
|
error_text = 'Inferno only supports one subscription per test run. Subscription already created with ' \
|
|
36
38
|
"ID #{subscription_hash['id']}"
|
|
37
|
-
|
|
39
|
+
fhir_error_response(400, 'business-rule', error_text)
|
|
38
40
|
return
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -60,35 +62,5 @@ module SubscriptionsTestKit
|
|
|
60
62
|
def tags
|
|
61
63
|
[SUBSCRIPTION_CREATE_TAG]
|
|
62
64
|
end
|
|
63
|
-
|
|
64
|
-
def verify_subscription(subscription)
|
|
65
|
-
unless subscription.is_a? FHIR::Subscription
|
|
66
|
-
return operation_outcome('error', 'invalid', 'No recognized R4 Subscription in request body')
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
unless subscription.channel&.type == 'rest-hook'
|
|
70
|
-
return operation_outcome('error', 'business-rule', 'channel.type must be rest-hook')
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
unless valid_url?(subscription.channel&.endpoint)
|
|
74
|
-
return operation_outcome('error', 'value', 'channel.endpoint is not recognized as a conformant URL')
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
heartbeat_period = find_heartbeat_period(subscription)
|
|
78
|
-
operation_outcome('error', 'not-supported', 'heartbeatPeriod is not supported') unless heartbeat_period.nil?
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def find_heartbeat_period(subscription)
|
|
82
|
-
subscription.channel&.extension&.find do |e|
|
|
83
|
-
e.url == 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-heartbeat-period'
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def valid_url?(url)
|
|
88
|
-
uri = URI.parse(url)
|
|
89
|
-
%w[http https].include?(uri.scheme)
|
|
90
|
-
rescue URI::InvalidURIError
|
|
91
|
-
false
|
|
92
|
-
end
|
|
93
65
|
end
|
|
94
66
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require_relative '../tags'
|
|
2
|
+
require_relative '../suites/subscriptions_r5_backport_r4_client/common/subscription_simulation_utils'
|
|
3
|
+
|
|
4
|
+
module SubscriptionsTestKit
|
|
5
|
+
class SubscriptionDeleteEndpoint < Inferno::DSL::SuiteEndpoint
|
|
6
|
+
include SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
|
7
|
+
|
|
8
|
+
error_response_format :operation_outcome
|
|
9
|
+
|
|
10
|
+
def test_run_identifier
|
|
11
|
+
request.headers['authorization']&.delete_prefix('Bearer ')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def make_response
|
|
15
|
+
unless subscription_change_allowed?
|
|
16
|
+
reject_subscription_change
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
subscription_id = request.params[:id]
|
|
21
|
+
subscription = find_subscription(test_run.test_session_id)
|
|
22
|
+
|
|
23
|
+
if subscription.present? && subscription.id == subscription_id
|
|
24
|
+
# Deleting an already deleted resource has no effect, so respond the same way
|
|
25
|
+
response.status = 204
|
|
26
|
+
else
|
|
27
|
+
fhir_error_response(404, 'not-found', "No subscription exists with ID #{subscription_id}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Rejected changes are left untagged so that they are not treated as part of the update and delete tests
|
|
32
|
+
def tags
|
|
33
|
+
return [] unless subscription_change_allowed?
|
|
34
|
+
|
|
35
|
+
[SUBSCRIPTION_DELETE_TAG, request.params[:id]]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'hana'
|
|
2
|
+
require_relative 'subscription_update_endpoint'
|
|
3
|
+
|
|
4
|
+
module SubscriptionsTestKit
|
|
5
|
+
# Supports updates via JSON Patch (https://hl7.org/fhir/R4/http.html#patch). Other patch formats
|
|
6
|
+
# (XML Patch and FHIRPath Patch) are not supported.
|
|
7
|
+
class SubscriptionPatchEndpoint < SubscriptionUpdateEndpoint
|
|
8
|
+
# Not inherited from SubscriptionUpdateEndpoint, so it must be declared here as well
|
|
9
|
+
error_response_format :operation_outcome
|
|
10
|
+
|
|
11
|
+
def requested_subscription_hash(current_subscription)
|
|
12
|
+
# Media types are case-insensitive
|
|
13
|
+
unless request.content_type&.downcase&.start_with?(JSON_PATCH_MIME_TYPE)
|
|
14
|
+
fhir_error_response(415, 'not-supported',
|
|
15
|
+
"Inferno only supports JSON Patch requests with Content-Type '#{JSON_PATCH_MIME_TYPE}'")
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
patch = parse_json_patch(request.body.string)
|
|
20
|
+
if patch.nil?
|
|
21
|
+
fhir_error_response(400, 'invalid', 'Request body is not a valid JSON Patch document')
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Apply the patch to the Subscription as the client would see it on a read
|
|
26
|
+
current_status = current_subscription_status_code(current_subscription)
|
|
27
|
+
current_hash = current_subscription.source_hash.deep_dup.merge('status' => current_status)
|
|
28
|
+
apply_json_patch(patch, current_hash)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The Subscription hash with the patch applied, or nil if an error response has been set
|
|
32
|
+
def apply_json_patch(patch, subscription_hash)
|
|
33
|
+
patched_hash = Hana::Patch.new(patch).apply(subscription_hash)
|
|
34
|
+
return patched_hash if patched_hash.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
fhir_error_response(422, 'processing', 'Applying the JSON Patch did not produce a JSON object')
|
|
37
|
+
nil
|
|
38
|
+
# Besides its own exceptions, Hana raises plain Ruby errors for some patches it cannot apply, e.g., an
|
|
39
|
+
# IndexError for a copy from a missing array index or a TypeError for a path through a number
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
fhir_error_response(422, 'processing', "Unable to apply JSON Patch: #{e.message}")
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_json_patch(body)
|
|
46
|
+
patch = JSON.parse(body)
|
|
47
|
+
return unless patch.is_a?(Array) && patch.all? { |operation| valid_json_patch_operation?(operation) }
|
|
48
|
+
|
|
49
|
+
patch
|
|
50
|
+
rescue JSON::ParserError
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Checks the members each operation requires (https://datatracker.ietf.org/doc/html/rfc6902#section-4)
|
|
55
|
+
# so that malformed operations are rejected here rather than raising non-patch errors when applied
|
|
56
|
+
def valid_json_patch_operation?(operation)
|
|
57
|
+
return false unless operation.is_a?(Hash) && operation['path'].is_a?(String)
|
|
58
|
+
|
|
59
|
+
case operation['op']
|
|
60
|
+
when 'add', 'replace', 'test'
|
|
61
|
+
operation.key?('value')
|
|
62
|
+
when 'move', 'copy'
|
|
63
|
+
operation['from'].is_a?(String)
|
|
64
|
+
else
|
|
65
|
+
operation['op'] == 'remove'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -5,6 +5,8 @@ module SubscriptionsTestKit
|
|
|
5
5
|
class SubscriptionReadEndpoint < Inferno::DSL::SuiteEndpoint
|
|
6
6
|
include SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
|
7
7
|
|
|
8
|
+
error_response_format :operation_outcome
|
|
9
|
+
|
|
8
10
|
def test_run_identifier
|
|
9
11
|
request.headers['authorization']&.delete_prefix('Bearer ')
|
|
10
12
|
end
|
|
@@ -13,15 +15,13 @@ module SubscriptionsTestKit
|
|
|
13
15
|
response.format = :json
|
|
14
16
|
subscription_id = request.params[:id]
|
|
15
17
|
|
|
16
|
-
subscription =
|
|
18
|
+
subscription = find_current_subscription(test_run.test_session_id)
|
|
17
19
|
|
|
18
20
|
if subscription.present? && subscription.id == subscription_id
|
|
19
|
-
status_code =
|
|
21
|
+
status_code = current_subscription_status_code(subscription)
|
|
20
22
|
response.body = subscription.source_hash.merge('status' => status_code).to_json
|
|
21
23
|
else
|
|
22
|
-
|
|
23
|
-
response.body = operation_outcome('error', 'not-found',
|
|
24
|
-
"No subscription exists with ID #{subscription_id}").to_json
|
|
24
|
+
fhir_error_response(404, 'not-found', "No subscription exists with ID #{subscription_id}")
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -5,33 +5,35 @@ module SubscriptionsTestKit
|
|
|
5
5
|
class SubscriptionStatusEndpoint < Inferno::DSL::SuiteEndpoint
|
|
6
6
|
include SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
|
7
7
|
|
|
8
|
+
error_response_format :operation_outcome
|
|
9
|
+
|
|
8
10
|
def test_run_identifier
|
|
9
11
|
request.headers['authorization']&.delete_prefix('Bearer ')
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def make_response
|
|
13
15
|
response.format = :json
|
|
14
|
-
subscription =
|
|
16
|
+
subscription = find_current_subscription(test_run.test_session_id)
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
subscription_id = request.params[:id]
|
|
19
|
+
if subscription.blank? || (subscription_id.present? && subscription_id != subscription.id)
|
|
20
|
+
fhir_error_response(404, 'not-found')
|
|
18
21
|
return
|
|
19
22
|
end
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
status_code = current_subscription_status_code(subscription)
|
|
22
25
|
|
|
23
26
|
# Handle resource-level status params
|
|
24
27
|
unless subscription_id.present?
|
|
25
28
|
begin
|
|
26
29
|
params = FHIR.from_contents(request.body.string)
|
|
27
30
|
rescue StandardError
|
|
28
|
-
|
|
29
|
-
response.body = operation_outcome('error', 'invalid', 'Invalid Parameters in request body').to_json
|
|
31
|
+
fhir_error_response(400, 'invalid', 'Invalid Parameters in request body')
|
|
30
32
|
return
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
unless subscription_params_match?(params, subscription)
|
|
34
|
-
|
|
35
|
+
unless subscription_params_match?(params, subscription, status_code)
|
|
36
|
+
fhir_error_response(404, 'not-found')
|
|
35
37
|
return
|
|
36
38
|
end
|
|
37
39
|
end
|
|
@@ -39,32 +41,25 @@ module SubscriptionsTestKit
|
|
|
39
41
|
notification_json = notification_bundle_input(result)
|
|
40
42
|
subscription_url = "#{base_subscription_url}/#{subscription.id}"
|
|
41
43
|
subscription_topic = subscription.criteria
|
|
42
|
-
status_code = determine_subscription_status_code(subscription_id)
|
|
43
44
|
event_count = determine_event_count(test_run.test_session_id)
|
|
44
45
|
response.status = 200
|
|
45
46
|
response.body = derive_status_bundle(notification_json, subscription_url, subscription_topic, status_code,
|
|
46
47
|
event_count, request.url).to_json
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
def subscription_params_match?(params, subscription)
|
|
50
|
+
def subscription_params_match?(params, subscription, status_code)
|
|
50
51
|
id_params = find_params(params, 'id')
|
|
51
52
|
|
|
52
53
|
return false if id_params&.any? && id_params&.none? { |p| p.valueString == subscription.id }
|
|
53
54
|
|
|
54
55
|
status_params = find_params(params, 'status')
|
|
55
|
-
|
|
56
|
-
status_params.blank? || status_params.any? { |p| p.valueString == subscription_status }
|
|
56
|
+
status_params.blank? || status_params.any? { |p| p.valueString == status_code }
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def tags
|
|
60
60
|
[SUBSCRIPTION_STATUS_TAG]
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def not_found
|
|
64
|
-
response.status = 404
|
|
65
|
-
response.body = operation_outcome('error', 'not-found').to_json
|
|
66
|
-
end
|
|
67
|
-
|
|
68
63
|
def find_params(params, name)
|
|
69
64
|
params&.parameter&.filter { |p| p.name == name }
|
|
70
65
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require_relative '../tags'
|
|
2
|
+
require_relative '../suites/subscriptions_r5_backport_r4_client/common/subscription_simulation_utils'
|
|
3
|
+
|
|
4
|
+
module SubscriptionsTestKit
|
|
5
|
+
class SubscriptionUpdateEndpoint < Inferno::DSL::SuiteEndpoint
|
|
6
|
+
include SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
|
7
|
+
|
|
8
|
+
error_response_format :operation_outcome
|
|
9
|
+
|
|
10
|
+
def test_run_identifier
|
|
11
|
+
request.headers['authorization']&.delete_prefix('Bearer ')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def make_response
|
|
15
|
+
if subscription_change_allowed?
|
|
16
|
+
update_subscription
|
|
17
|
+
else
|
|
18
|
+
reject_subscription_change
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_subscription
|
|
23
|
+
response.format = :json
|
|
24
|
+
subscription_id = request.params[:id]
|
|
25
|
+
|
|
26
|
+
current_subscription = find_current_subscription(test_run.test_session_id)
|
|
27
|
+
unless current_subscription.present? && current_subscription.id == subscription_id
|
|
28
|
+
fhir_error_response(404, 'not-found', "No subscription exists with ID #{subscription_id}")
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# We have to manipulate the raw hash so that we don't lose the _payload primitive extension
|
|
33
|
+
subscription_hash = requested_subscription_hash(current_subscription)
|
|
34
|
+
return if subscription_hash.nil?
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
subscription = FHIR.from_contents(subscription_hash.to_json)
|
|
38
|
+
rescue StandardError
|
|
39
|
+
subscription = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
verification_outcome = verify_subscription(subscription)
|
|
43
|
+
if verification_outcome.present?
|
|
44
|
+
response.status = 400
|
|
45
|
+
response.body = verification_outcome.to_json
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless subscription.id == subscription_id
|
|
50
|
+
fhir_error_response(400, 'invalid', "Updated Subscription id '#{subscription.id}' does not match the id " \
|
|
51
|
+
"'#{subscription_id}' in the request URL")
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
subscription_hash['status'] = current_subscription_status_code(subscription)
|
|
56
|
+
subscription_hash['channel']['payload'] = actual_mime_type(subscription)
|
|
57
|
+
response.status = 200
|
|
58
|
+
response.body = subscription_hash.to_json
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The requested state of the Subscription as a raw hash, or nil if an error response has been set
|
|
62
|
+
def requested_subscription_hash(_current_subscription)
|
|
63
|
+
subscription_hash = begin
|
|
64
|
+
JSON.parse(request.body.string)
|
|
65
|
+
rescue JSON::ParserError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
return subscription_hash if subscription_hash.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
fhir_error_response(400, 'invalid', 'No recognized R4 Subscription in request body')
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Rejected changes are left untagged so that they are not treated as part of the update and delete tests
|
|
75
|
+
def tags
|
|
76
|
+
return [] unless subscription_change_allowed?
|
|
77
|
+
|
|
78
|
+
[SUBSCRIPTION_UPDATE_TAG, request.params[:id]]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SubscriptionsTestKit
|
|
4
|
+
# Values shared by the FHIR validators that the client and server suites define
|
|
5
|
+
# Validator definitions kept in the suites rather than pulled out into this module
|
|
6
|
+
# to keep them local to the suites even though identical.
|
|
7
|
+
SUBSCRIPTIONS_BACKPORT_IG = 'hl7.fhir.uv.subscriptions-backport#1.1.0'
|
|
8
|
+
UNRESOLVED_URL_VALIDATION_MESSAGE = /\A\S+: \S+: URL value '.*' does not resolve/
|
|
9
|
+
end
|
|
@@ -103,8 +103,8 @@ hl7.fhir.uv.subscriptions_1.1.0,122,https://hl7.org/fhir/uv/subscriptions-backpo
|
|
|
103
103
|
hl7.fhir.uv.subscriptions_1.1.0,123,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,MAY support the $get-ws-binding-token operation,MAY,Server,,Not Tested,Used for Websocket channels that Inferno does not currently support,NA,NA
|
|
104
104
|
hl7.fhir.uv.subscriptions_1.1.0,124,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server SHALL be capable of returning a Subscription resource using: GET [base]/Subscription/[id],SHALL,Server,,,,"",""
|
|
105
105
|
hl7.fhir.uv.subscriptions_1.1.0,125,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server SHOULD be capable of creating a Subscription resource using either: POST [base]/Subscription or PUT [base]/Subscription/[id],SHOULD,Server,,,,"",""
|
|
106
|
-
hl7.fhir.uv.subscriptions_1.1.0,126,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server SHOULD be capable of modifying a Subscription resource using either: PUT [base]/Subscription/[id] or PATCH [base]/Subscription/[id],SHOULD,Server,,,,"",""
|
|
107
|
-
hl7.fhir.uv.subscriptions_1.1.0,127,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server SHOULD be capable of deleting a Subscription resource using: DELETE [base]/Subscription/[id],SHOULD,Server,,,,
|
|
106
|
+
hl7.fhir.uv.subscriptions_1.1.0,126,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server SHOULD be capable of modifying a Subscription resource using either: PUT [base]/Subscription/[id] or PATCH [base]/Subscription/[id],SHOULD,Server,,,,"7.01, 7.02","subscriptions_r5_backport_r4_server-subscriptions_r4_server_update_delete-subscriptions_r4_server_update_put, subscriptions_r5_backport_r4_server-subscriptions_r4_server_update_delete-subscriptions_r4_server_update_patch"
|
|
107
|
+
hl7.fhir.uv.subscriptions_1.1.0,127,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server SHOULD be capable of deleting a Subscription resource using: DELETE [base]/Subscription/[id],SHOULD,Server,,,,7.03,subscriptions_r5_backport_r4_server-subscriptions_r4_server_update_delete-subscriptions_r4_server_delete
|
|
108
108
|
hl7.fhir.uv.subscriptions_1.1.0,128,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,A Server [MAY] be capable of searching for Subscription resources using: GET [base]/Subscription/?[parameters],MAY,Server,,,,"",""
|
|
109
109
|
hl7.fhir.uv.subscriptions_1.1.0,129,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,Search Parameter Summary: SHALL [support] url,SHALL,Server,,,,"",""
|
|
110
110
|
hl7.fhir.uv.subscriptions_1.1.0,130,https://hl7.org/fhir/uv/subscriptions-backport/STU1.1/CapabilityStatement-backport-subscription-server-r4.html#summary,Search Parameter Summary: SHOULD [support] status,SHOULD,Server,,,,"",""
|