davinci_pas_test_kit 0.12.0 → 0.12.1
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/davinci_pas_test_kit/client_suite.rb +24 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +29 -1
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +27 -4
- data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_pended_pas_inquiry_request_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_inquire_request_bundle_validation_test.rb} +22 -20
- data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_denial_pas_response_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_inquire_response_bundle_validation_test.rb} +34 -21
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +124 -5
- data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_pas_request_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_request_bundle_validation_test.rb} +22 -20
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/{pas_client_approval_submit_response_attest.rb → pas_client_response_attest.rb} +26 -9
- data/lib/davinci_pas_test_kit/{generated/v2.0.1/client_tests/client_pended_pas_response_bundle_validation_test.rb → custom_groups/v2.0.1/client_tests/pas_client_response_bundle_validation_test.rb} +44 -20
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +49 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +48 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +21 -9
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +2 -2
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +21 -22
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +97 -31
- data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +213 -72
- data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +85 -134
- data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +96 -0
- data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +90 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +0 -2
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +3 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +0 -2
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +3 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +0 -4
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +1 -1
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +6 -5
- data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +1 -0
- data/lib/davinci_pas_test_kit/generator/group_generator.rb +9 -8
- data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +7 -3
- data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +1 -0
- data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +19 -56
- data/lib/davinci_pas_test_kit/generator/value_extractor.rb +4 -1
- data/lib/davinci_pas_test_kit/generator.rb +1 -1
- data/lib/davinci_pas_test_kit/jobs/send_pas_subscription_notification.rb +136 -0
- data/lib/davinci_pas_test_kit/jobs/send_subscription_handshake.rb +139 -0
- data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +8 -7
- data/lib/davinci_pas_test_kit/response_generator.rb +397 -0
- data/lib/davinci_pas_test_kit/tags.rb +9 -0
- data/lib/davinci_pas_test_kit/urls.rb +8 -0
- data/lib/davinci_pas_test_kit/user_input_response.rb +11 -8
- data/lib/davinci_pas_test_kit/validation_test.rb +0 -1
- data/lib/davinci_pas_test_kit/version.rb +2 -2
- metadata +30 -14
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +0 -38
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +0 -39
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +0 -35
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +0 -39
- data/lib/davinci_pas_test_kit/generator/templates/validation_client.rb.erb +0 -50
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../tags'
|
4
|
+
require_relative '../urls'
|
5
|
+
require 'subscriptions_test_kit'
|
6
|
+
|
7
|
+
module DaVinciPASTestKit
|
8
|
+
module Jobs
|
9
|
+
class SendPASSubscriptionNotification
|
10
|
+
include Sidekiq::Job
|
11
|
+
include SubscriptionsTestKit::SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
12
|
+
include URLs
|
13
|
+
|
14
|
+
# override the one from URLs
|
15
|
+
def suite_id
|
16
|
+
@notification_suite_id
|
17
|
+
end
|
18
|
+
|
19
|
+
sidekiq_options retry: false
|
20
|
+
|
21
|
+
def perform(test_run_id, test_session_id, result_id, notification_bearer_token, notification_json, resume_token,
|
22
|
+
notification_suite_id)
|
23
|
+
@test_run_id = test_run_id
|
24
|
+
@test_session_id = test_session_id
|
25
|
+
@result_id = result_id
|
26
|
+
@notification_bearer_token = notification_bearer_token
|
27
|
+
@notification_json = notification_json
|
28
|
+
@resume_token = resume_token
|
29
|
+
@notification_suite_id = notification_suite_id
|
30
|
+
|
31
|
+
await_subscription_creation # NOTE: currently must exist - see PASClientPendedSubmitTest
|
32
|
+
sleep 1
|
33
|
+
return unless test_still_waiting?
|
34
|
+
|
35
|
+
sleep rand(5..10)
|
36
|
+
return unless test_still_waiting?
|
37
|
+
|
38
|
+
send_event_notification
|
39
|
+
end
|
40
|
+
|
41
|
+
def requests_repo
|
42
|
+
@requests_repo ||= Inferno::Repositories::Requests.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def results_repo
|
46
|
+
@results_repo ||= Inferno::Repositories::Results.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def subscription
|
50
|
+
@subscription ||= find_subscription(@test_session_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def subscription_notification_endpoint
|
54
|
+
subscription&.channel&.endpoint
|
55
|
+
end
|
56
|
+
|
57
|
+
def headers
|
58
|
+
@headers ||= subscription_headers.merge(content_type_header).merge(authorization_header)
|
59
|
+
end
|
60
|
+
|
61
|
+
def rest_hook_connection
|
62
|
+
@rest_hook_connection ||= Faraday.new(url: subscription_notification_endpoint, request: { open_timeout: 30 },
|
63
|
+
headers:)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_suite_connection
|
67
|
+
@test_suite_connection ||= Faraday.new(base_url)
|
68
|
+
end
|
69
|
+
|
70
|
+
def content_type_header
|
71
|
+
@content_type_header ||= { 'Content-Type' => actual_mime_type(subscription) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def subscription_headers
|
75
|
+
@subscription_headers ||= subscription.channel&.header&.each_with_object({}) do |header, hash|
|
76
|
+
header_name, header_value = header.split(': ', 2)
|
77
|
+
hash[header_name] = header_value
|
78
|
+
end || {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def authorization_header
|
82
|
+
@authorization_header ||=
|
83
|
+
@notification_bearer_token.present? ? { 'Authorization' => "Bearer #{@notification_bearer_token}" } : {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def subscription_topic
|
87
|
+
@subscription_topic ||= subscription&.criteria
|
88
|
+
end
|
89
|
+
|
90
|
+
def subscription_full_url
|
91
|
+
@subscription_full_url ||= "#{fhir_subscription_url}/#{subscription.id}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_still_waiting?
|
95
|
+
results_repo.find_waiting_result(test_run_id: @test_run_id)
|
96
|
+
end
|
97
|
+
|
98
|
+
def await_subscription_creation
|
99
|
+
sleep 0.5 until subscription.present? || !test_still_waiting?
|
100
|
+
end
|
101
|
+
|
102
|
+
def send_event_notification
|
103
|
+
event_json = derive_event_notification(@notification_json, subscription_full_url, subscription_topic, 1).to_json
|
104
|
+
response = send_notification(event_json)
|
105
|
+
persist_notification_request(response, [REST_HOOK_EVENT_NOTIFICATION_TAG])
|
106
|
+
end
|
107
|
+
|
108
|
+
def send_notification(request_body)
|
109
|
+
rest_hook_connection.post('', request_body)
|
110
|
+
rescue Faraday::Error => e
|
111
|
+
# Warning: This is a hack. If there is an error with the request such that we never get a response, we have
|
112
|
+
# no clean way to persist that information for the Inferno test to check later. The solution here
|
113
|
+
# is to persist the request anyway with a status of nil, using the error message as response body
|
114
|
+
Faraday::Response.new(response_body: e.message, url: rest_hook_connection.url_prefix.to_s)
|
115
|
+
end
|
116
|
+
|
117
|
+
def persist_notification_request(response, tags)
|
118
|
+
inferno_request_headers = headers.map { |name, value| { name:, value: } }
|
119
|
+
inferno_response_headers = response.headers&.map { |name, value| { name:, value: } }
|
120
|
+
requests_repo.create(
|
121
|
+
verb: 'POST',
|
122
|
+
url: response.env.url.to_s,
|
123
|
+
direction: 'outgoing',
|
124
|
+
status: response.status,
|
125
|
+
request_body: response.env.request_body,
|
126
|
+
response_body: response.env.response_body,
|
127
|
+
test_session_id: @test_session_id,
|
128
|
+
result_id: @result_id,
|
129
|
+
request_headers: inferno_request_headers,
|
130
|
+
response_headers: inferno_response_headers,
|
131
|
+
tags:
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../tags'
|
4
|
+
require 'subscriptions_test_kit'
|
5
|
+
|
6
|
+
module DaVinciPASTestKit
|
7
|
+
module Jobs
|
8
|
+
class SendSubscriptionHandshake
|
9
|
+
include Sidekiq::Job
|
10
|
+
include SubscriptionsTestKit::SubscriptionsR5BackportR4Client::SubscriptionSimulationUtils
|
11
|
+
|
12
|
+
sidekiq_options retry: false
|
13
|
+
|
14
|
+
def perform(test_run_id, test_session_id, result_id, subscription_id, subscription_url, client_endpoint,
|
15
|
+
bearer_token, notification_json, test_run_identifier, test_suite_base_url)
|
16
|
+
@test_run_id = test_run_id
|
17
|
+
@test_session_id = test_session_id
|
18
|
+
@result_id = result_id
|
19
|
+
@subscription_id = subscription_id
|
20
|
+
@subscription_url = subscription_url
|
21
|
+
@client_endpoint = client_endpoint
|
22
|
+
@bearer_token = bearer_token
|
23
|
+
@notification_json = notification_json.present? ? notification_json : default_notification_base_json
|
24
|
+
@test_run_identifier = test_run_identifier
|
25
|
+
@test_suite_base_url = test_suite_base_url
|
26
|
+
|
27
|
+
await_subscription_creation
|
28
|
+
sleep 1
|
29
|
+
return unless test_still_waiting?
|
30
|
+
|
31
|
+
send_handshake_notification
|
32
|
+
test_suite_connection.get(RESUME_PASS_PATH.delete_prefix('/'), { token: @test_run_identifier })
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_notification_base_json
|
36
|
+
FHIR::Bundle.new(
|
37
|
+
timestamp: Time.now.utc.iso8601,
|
38
|
+
type: 'history',
|
39
|
+
entry: [
|
40
|
+
FHIR::Bundle::Entry.new(fullUrl: "urn:uuid:#{SecureRandom.uuid}",
|
41
|
+
resource: FHIR.from_contents(default_handshake_parameters_base_json))
|
42
|
+
]
|
43
|
+
).to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_handshake_parameters_base_json
|
47
|
+
'{ "parameter": [ { "name": "subscription", "valueReference": { "reference": "replace_with_subscription_ref" } }, { "name": "topic", "valueCanonical": "replace_with_topic_canonical" }, { "name": "status", "valueCode": "requested" }, { "name": "type", "valueCode": "handshake" }, { "name": "events-since-subscription-start", "valueString": "0" } ], "resourceType": "Parameters" }' # rubocop:disable Layout/LineLength
|
48
|
+
end
|
49
|
+
|
50
|
+
def requests_repo
|
51
|
+
@requests_repo ||= Inferno::Repositories::Requests.new
|
52
|
+
end
|
53
|
+
|
54
|
+
def results_repo
|
55
|
+
@results_repo ||= Inferno::Repositories::Results.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def subscription
|
59
|
+
@subscription ||= find_subscription(@test_session_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def headers
|
63
|
+
@headers ||= subscription_headers.merge(content_type_header).merge(authorization_header)
|
64
|
+
end
|
65
|
+
|
66
|
+
def rest_hook_connection
|
67
|
+
@rest_hook_connection ||= Faraday.new(url: @client_endpoint, request: { open_timeout: 30 }, headers:)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_suite_connection
|
71
|
+
@test_suite_connection ||= Faraday.new(@test_suite_base_url)
|
72
|
+
end
|
73
|
+
|
74
|
+
def content_type_header
|
75
|
+
@content_type_header ||= { 'Content-Type' => actual_mime_type(subscription) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def subscription_headers
|
79
|
+
return {} unless subscription.present?
|
80
|
+
|
81
|
+
@subscription_headers ||= subscription.channel&.header&.each_with_object({}) do |header, hash|
|
82
|
+
header_name, header_value = header.split(': ', 2)
|
83
|
+
hash[header_name] = header_value
|
84
|
+
end || {}
|
85
|
+
end
|
86
|
+
|
87
|
+
def subscription_topic
|
88
|
+
@subscription_topic ||= subscription&.criteria
|
89
|
+
end
|
90
|
+
|
91
|
+
def authorization_header
|
92
|
+
@authorization_header ||= @bearer_token.present? ? { 'Authorization' => "Bearer #{@bearer_token}" } : {}
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_still_waiting?
|
96
|
+
results_repo.find_waiting_result(test_run_id: @test_run_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
def await_subscription_creation
|
100
|
+
sleep 0.5 until subscription.present?
|
101
|
+
end
|
102
|
+
|
103
|
+
def send_handshake_notification
|
104
|
+
handshake_json = derive_handshake_notification(@notification_json, @subscription_url,
|
105
|
+
subscription_topic).to_json
|
106
|
+
response = send_notification(handshake_json)
|
107
|
+
persist_notification_request(response, [REST_HOOK_HANDSHAKE_NOTIFICATION_TAG])
|
108
|
+
response
|
109
|
+
end
|
110
|
+
|
111
|
+
def send_notification(request_body)
|
112
|
+
rest_hook_connection.post('', request_body)
|
113
|
+
rescue Faraday::Error => e
|
114
|
+
# Warning: This is a hack. If there is an error with the request such that we never get a response, we have
|
115
|
+
# no clean way to persist that information for the Inferno test to check later. The solution here
|
116
|
+
# is to persist the request anyway with a status of nil, using the error message as response body
|
117
|
+
Faraday::Response.new(response_body: e.message, url: rest_hook_connection.url_prefix.to_s)
|
118
|
+
end
|
119
|
+
|
120
|
+
def persist_notification_request(response, tags)
|
121
|
+
inferno_request_headers = headers.map { |name, value| { name:, value: } }
|
122
|
+
inferno_response_headers = response.headers&.map { |name, value| { name:, value: } }
|
123
|
+
requests_repo.create(
|
124
|
+
verb: 'POST',
|
125
|
+
url: response.env.url.to_s,
|
126
|
+
direction: 'outgoing',
|
127
|
+
status: response.status,
|
128
|
+
request_body: response.env.request_body,
|
129
|
+
response_body: response.env.response_body,
|
130
|
+
test_session_id: @test_session_id,
|
131
|
+
result_id: @result_id,
|
132
|
+
request_headers: inferno_request_headers,
|
133
|
+
response_headers: inferno_response_headers,
|
134
|
+
tags:
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -171,7 +171,7 @@ module DaVinciPASTestKit
|
|
171
171
|
bundle_entry = bundle.entry
|
172
172
|
|
173
173
|
root_entry = bundle_entry.find do |entry|
|
174
|
-
|
174
|
+
['Claim', 'ClaimResponse'].include?(entry.resource.resourceType)
|
175
175
|
end
|
176
176
|
|
177
177
|
if root_entry.present?
|
@@ -331,7 +331,9 @@ module DaVinciPASTestKit
|
|
331
331
|
# @param bundle_entry [Array] The bundle.entry contents.
|
332
332
|
# @param version [String] The IG version.
|
333
333
|
def add_declared_profiles(instance, bundle_entry, version)
|
334
|
-
instance.resource
|
334
|
+
return unless instance.resource.present?
|
335
|
+
|
336
|
+
instance.resource.meta&.profile&.each do |url|
|
335
337
|
next if bundle_resources_target_profile_map[instance.fullUrl][:profile_urls].include?(url)
|
336
338
|
|
337
339
|
bundle_resources_target_profile_map[instance.fullUrl][:profile_urls] << url
|
@@ -462,11 +464,10 @@ module DaVinciPASTestKit
|
|
462
464
|
return if ref.blank?
|
463
465
|
|
464
466
|
absolute_ref = absolute_url(ref, base_url)
|
465
|
-
resource_type, resource_id = ref.split('/')
|
466
467
|
matching_resources = resources_to_match.find_all { |res| res.fullUrl == absolute_ref }
|
467
468
|
|
468
469
|
if matching_resources.length != 1
|
469
|
-
validation_error_messages << resource_shall_appear_once_message(
|
470
|
+
validation_error_messages << resource_shall_appear_once_message(absolute_ref,
|
470
471
|
matching_resources.length)
|
471
472
|
end
|
472
473
|
|
@@ -525,10 +526,10 @@ module DaVinciPASTestKit
|
|
525
526
|
#
|
526
527
|
# This method generates an error message when a referenced resource appears more than once
|
527
528
|
# in a FHIR bundle, which is not allowed.
|
528
|
-
def resource_shall_appear_once_message(
|
529
|
+
def resource_shall_appear_once_message(absolute_ref, total_matches)
|
529
530
|
"
|
530
|
-
The referenced #{
|
531
|
-
SHALL
|
531
|
+
The referenced #{absolute_ref} resource
|
532
|
+
SHALL appear exactly once in the Bundle, but found #{total_matches}.
|
532
533
|
"
|
533
534
|
end
|
534
535
|
|