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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/davinci_pas_test_kit/client_suite.rb +24 -0
  3. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +29 -1
  4. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +27 -4
  5. 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
  6. 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
  7. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +124 -5
  8. 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
  9. 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
  10. 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
  11. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +49 -0
  12. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +48 -0
  13. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +21 -9
  14. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +2 -2
  15. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +21 -22
  16. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +97 -31
  17. data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +213 -72
  18. data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +85 -134
  19. data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +96 -0
  20. data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +90 -0
  21. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +0 -2
  22. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +3 -1
  23. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -1
  24. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +0 -2
  25. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +3 -1
  26. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +0 -4
  27. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -1
  28. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +1 -1
  29. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +6 -5
  30. data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +1 -0
  31. data/lib/davinci_pas_test_kit/generator/group_generator.rb +9 -8
  32. data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +7 -3
  33. data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +1 -0
  34. data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +19 -56
  35. data/lib/davinci_pas_test_kit/generator/value_extractor.rb +4 -1
  36. data/lib/davinci_pas_test_kit/generator.rb +1 -1
  37. data/lib/davinci_pas_test_kit/jobs/send_pas_subscription_notification.rb +136 -0
  38. data/lib/davinci_pas_test_kit/jobs/send_subscription_handshake.rb +139 -0
  39. data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +8 -7
  40. data/lib/davinci_pas_test_kit/response_generator.rb +397 -0
  41. data/lib/davinci_pas_test_kit/tags.rb +9 -0
  42. data/lib/davinci_pas_test_kit/urls.rb +8 -0
  43. data/lib/davinci_pas_test_kit/user_input_response.rb +11 -8
  44. data/lib/davinci_pas_test_kit/validation_test.rb +0 -1
  45. data/lib/davinci_pas_test_kit/version.rb +2 -2
  46. metadata +30 -14
  47. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +0 -38
  48. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +0 -39
  49. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +0 -35
  50. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +0 -39
  51. 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
- entry.resource.resourceType == 'Claim' || entry.resource.resourceType == 'ClaimResponse'
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&.meta&.profile&.each do |url|
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(resource_type, resource_id,
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(reference_resource_type, reference_resource_id, total_matches)
529
+ def resource_shall_appear_once_message(absolute_ref, total_matches)
529
530
  "
530
- The referenced #{reference_resource_type}/#{reference_resource_id} resource
531
- SHALL only appear once in the Bundle, but found #{total_matches}.
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