davinci_pas_test_kit 0.11.1 → 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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/pas_client_example.json +30 -0
  3. data/config/presets/pas_ri.json +69 -0
  4. data/lib/davinci_pas_test_kit/client_suite.rb +28 -2
  5. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +29 -1
  6. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +27 -4
  7. 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
  8. 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
  9. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +124 -5
  10. 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
  11. 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
  12. 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
  13. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +49 -0
  14. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +48 -0
  15. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +21 -9
  16. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +2 -2
  17. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +21 -22
  18. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +97 -31
  19. data/lib/davinci_pas_test_kit/docs/PAS Requirements Interpretation.xlsx +0 -0
  20. data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +213 -72
  21. data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +85 -134
  22. data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +96 -0
  23. data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +90 -0
  24. data/lib/davinci_pas_test_kit/fhir_resource_navigation.rb +3 -3
  25. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +0 -2
  26. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +3 -1
  27. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -1
  28. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +0 -2
  29. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +3 -1
  30. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +0 -4
  31. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -1
  32. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +1 -1
  33. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +6 -5
  34. data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +5 -2
  35. data/lib/davinci_pas_test_kit/generator/group_generator.rb +9 -8
  36. data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +7 -3
  37. data/lib/davinci_pas_test_kit/generator/templates/group.rb.erb +129 -0
  38. data/lib/davinci_pas_test_kit/generator/templates/must_support.rb.erb +73 -0
  39. data/lib/davinci_pas_test_kit/generator/templates/operation.rb.erb +62 -0
  40. data/lib/davinci_pas_test_kit/generator/templates/resource_list.rb.erb +13 -0
  41. data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +92 -0
  42. data/lib/davinci_pas_test_kit/generator/templates/validation.rb.erb +98 -0
  43. data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +19 -56
  44. data/lib/davinci_pas_test_kit/generator/value_extractor.rb +4 -1
  45. data/lib/davinci_pas_test_kit/generator.rb +1 -1
  46. data/lib/davinci_pas_test_kit/igs/davinci_pas_2.0.1.tgz +0 -0
  47. data/lib/davinci_pas_test_kit/jobs/send_pas_subscription_notification.rb +136 -0
  48. data/lib/davinci_pas_test_kit/jobs/send_subscription_handshake.rb +139 -0
  49. data/lib/davinci_pas_test_kit/metadata.rb +87 -0
  50. data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +8 -7
  51. data/lib/davinci_pas_test_kit/response_generator.rb +397 -0
  52. data/lib/davinci_pas_test_kit/tags.rb +9 -0
  53. data/lib/davinci_pas_test_kit/urls.rb +8 -0
  54. data/lib/davinci_pas_test_kit/user_input_response.rb +11 -8
  55. data/lib/davinci_pas_test_kit/validation_test.rb +0 -1
  56. data/lib/davinci_pas_test_kit/version.rb +2 -1
  57. data/lib/davinci_pas_test_kit.rb +1 -0
  58. metadata +44 -15
  59. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +0 -38
  60. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +0 -39
  61. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +0 -35
  62. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +0 -39
@@ -0,0 +1,62 @@
1
+ module DaVinciPASTestKit
2
+ module <%= module_name %>
3
+ class <%= class_name %> < Inferno::Test
4
+
5
+ id :<%= test_id %>
6
+ title '<%= title %>'
7
+ description %(
8
+ <%=description.gsub("\n", "\n" + " "*8) %>
9
+ )
10
+
11
+ input :pa_<%= request_type %>_payload,
12
+ title: 'PAS <%= request_type.humanize.titleize%> Payload',
13
+ description: 'Insert Bundle to be sent for PAS <%= request_type.humanize.titleize%>',
14
+ type: 'textarea',
15
+ optional: true
16
+ input_order :server_endpoint, :smart_credentials
17
+ <% if operation == 'submit'%>output :response_time<% end %>
18
+ makes_request :pa_<%= operation %>
19
+
20
+ def scratch_resources
21
+ scratch[:<%= operation %>_request_response_pair] ||= {}
22
+ end
23
+
24
+ def request_bundles
25
+ parsed_payload = JSON.parse(pa_<%= request_type %>_payload)
26
+ [parsed_payload].flatten.compact.uniq
27
+ end
28
+
29
+ def perform_operation(request_payload)
30
+ <% if operation == 'submit'%>start_time = Time.now<% end %>
31
+ fhir_operation('/Claim/$<%= operation %>', body: request_payload, name: :pa_<%= operation %>)
32
+ <% if operation == 'submit'%>response_time = Time.now - start_time
33
+
34
+ if response_time > 15
35
+ warning %(
36
+ The server took more that 15 seconds to respond to the Prior Authorization
37
+ request.
38
+
39
+ Response Time: #{response_time}
40
+ )
41
+ end
42
+ <% end %>
43
+ assert_response_status([200, 201])
44
+ assert_valid_json(request.response_body)
45
+
46
+ # Save request/response pair
47
+ scratch_resources[:all] ||= []
48
+ scratch_resources[:all] << {request_bundle: request.request_body, response_bundle: resource}
49
+ <% if operation == 'submit'%>output response_time:<% end %>
50
+ end
51
+
52
+ run do
53
+ skip_if pa_<%= request_type %>_payload.blank?, 'No bundle request provided to perform the <%= operation %> operation'
54
+ assert_valid_json(pa_<%= request_type %>_payload)
55
+
56
+ request_bundles.each do |bundle|
57
+ perform_operation(bundle)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+ module DaVinciPASTestKit
2
+ module <%= module_name %>
3
+ module ResourceList
4
+ RESOURCE_LIST = [
5
+ <%= resource_list_string.gsub("\n", "\n" + " "*4) %>
6
+ ].freeze
7
+
8
+ RESOURCE_SUPPORTED_PROFILES = {<% resource_supported_profiles.each do |resource, profile_list| %>
9
+ <%= resource %>: <%= profile_list %>,<% end %>
10
+ }.freeze
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,92 @@
1
+ require 'subscriptions_test_kit'
2
+ require_relative '../../validator_suppressions'
3
+ require_relative '<%= error_group_file_name %>'
4
+ <% group_file_list.each do |file_name| %>require_relative '<%= file_name %>'
5
+ <% end %>
6
+ module DaVinciPASTestKit
7
+ module <%= module_name %>
8
+ class <%= class_name %> < Inferno::TestSuite
9
+ id :<%= suite_id %>
10
+ title '<%= title %>'
11
+ description File.read(File.join(__dir__, '..', '..', 'docs', 'server_suite_description_<%= ig_metadata.reformatted_version %>.md'))
12
+
13
+ links [
14
+ {
15
+ label: 'Report Issue',
16
+ url: 'https://github.com/inferno-framework/davinci-pas-test-kit/issues/'
17
+ },
18
+ {
19
+ label: 'Open Source',
20
+ url: 'https://github.com/inferno-framework/davinci-pas-test-kit/'
21
+ },
22
+ {
23
+ label: 'Download',
24
+ url: 'https://github.com/inferno-framework/davinci-pas-test-kit/releases'
25
+ },
26
+ {
27
+ label: 'Implementation Guide',
28
+ url: '<%= ig_link %>'
29
+ }
30
+ ]
31
+
32
+ resume_test_route :get, '/resume_after_notification' do |request|
33
+ request.query_parameters['token']
34
+ end
35
+
36
+ fhir_resource_validator do
37
+ igs '<%= ig_identifier %>'
38
+
39
+ exclude_message do |message|
40
+ # Messages expected of the form `<ResourceType>: <FHIRPath>: <message>`
41
+ # We strip `<ResourceType>: <FHIRPath>: ` for the sake of matching
42
+ SUPPRESSED_MESSAGES.match?(message.message.sub(/\A\S+: \S+: /, '')) ||
43
+ message.message.downcase.include?('x12')
44
+ end
45
+ end
46
+
47
+ input :server_endpoint,
48
+ title: 'FHIR Server Endpoint URL',
49
+ description: 'Insert the FHIR server endpoint URL for PAS'
50
+
51
+ input :smart_credentials,
52
+ title: 'OAuth Credentials',
53
+ type: :auth_info,
54
+ options: {
55
+ mode: 'access',
56
+ components: [
57
+ {
58
+ name: :auth_type,
59
+ default: 'backend_services'
60
+ }
61
+ ]
62
+ },
63
+ optional: true
64
+
65
+ fhir_client do
66
+ url :server_endpoint
67
+ auth_info :smart_credentials
68
+ end
69
+
70
+ # Used for attestation experiment - see pas_claim_response_decision_test.rb
71
+ # resume_test_route :get, RESUME_PASS_PATH do |request|
72
+ # request.query_parameters['token']
73
+ # end
74
+ #
75
+ # resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
76
+ # request.query_parameters['token']
77
+ # end
78
+
79
+ group 'Demonstrate Workflow Support' do
80
+ description %(
81
+ The workflow tests validate that the server can participate in complete
82
+ end-to-end prior authorization interactions, returning responses that are
83
+ conformant and also contain the correct codes.
84
+ )
85
+ <% group_id_list.each do |id| %>
86
+ group from: :<%= id %><% end %>
87
+ end
88
+ group from: :<%= must_support_group_id %>
89
+ group from: :<%= error_group_id %>
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,98 @@
1
+ require_relative '../../../pas_bundle_validation'
2
+
3
+ module DaVinciPASTestKit
4
+ module <%= module_name %>
5
+ class <%= class_name %> < Inferno::Test
6
+ include DaVinciPASTestKit::PasBundleValidation
7
+
8
+ id :<%= test_id %>
9
+ title '<%= title %>'
10
+ description %(
11
+ <%=description.strip.gsub("\n", "\n" + " "*8) %>
12
+ )
13
+ <% if request_type.include?('request') %>
14
+ input :pa_<%= request_type %>_payload,
15
+ title: 'PAS <%= request_type.humanize.titleize%> Payload',
16
+ description: 'Insert Bundle to be sent for PAS <%= request_type.humanize.titleize%>',
17
+ type: 'textarea',
18
+ optional: true
19
+
20
+ input_order :server_endpoint, :smart_credentials<% end %>
21
+ output :dar_code_found, :dar_extension_found
22
+
23
+ def resource_type
24
+ '<%= resource_type %>'
25
+ end
26
+
27
+ def scratch_resources
28
+ scratch[:<%= request_type %>_resources] ||= {}
29
+ end
30
+
31
+ def request_type
32
+ '<%= request_type.split('_').first %>'
33
+ end
34
+ <%if request_type.include?('response')%>
35
+ def target_request_response_pairs<% operation = request_type.split('_').first %>
36
+ scratch[:<%= operation %>_request_response_pair] ||= {}
37
+ scratch[:<%= operation %>_request_response_pair][:all] ||= []
38
+ end<% end %>
39
+
40
+ <%if request_type.include?('request')%>
41
+ def request_bundles
42
+ parsed_payload = JSON.parse(pa_<%= request_type %>_payload)
43
+ fhir_resources = [parsed_payload].flatten.compact.uniq.map { |payload| FHIR.from_contents(payload.to_json)}.compact
44
+ fhir_resources.select { |res| res.resourceType == 'Bundle'}
45
+ end<% end %>
46
+
47
+ run do<% if request_type.include?('request') %>
48
+ skip_if pa_<%= request_type %>_payload.blank?, 'No bundle request input provided.'
49
+ assert_valid_json(pa_<%= request_type %>_payload)
50
+ assert request_bundles.present?, 'Provided input is not a bundle or list of bundles'
51
+
52
+ save_bundles_and_entries_to_scratch(request_bundles)
53
+
54
+ request_bundles.each do |bundle|
55
+ perform_request_validation(
56
+ bundle,
57
+ '<%= profile_url %>',
58
+ '<%= profile_version %>',
59
+ request_type
60
+ )
61
+ end
62
+
63
+ validation_error_messages.each do |msg|
64
+ messages << { type: 'error', message: msg }
65
+ end
66
+ msg = 'Bundle(s) provided and/or entry resources are not conformant. Check messages for issues found.'
67
+ skip_if validation_error_messages.present?, msg
68
+ <% else %>
69
+ skip_if target_request_response_pairs.blank?, 'No <%= request_type.split('_').first%> response to validate. Either no <%= request_type.split('_').first%> request was made in a previous test or it resulted in a server error.'
70
+ target_pairs = target_request_response_pairs
71
+ # Clean request/response pair after validatation
72
+ scratch[:<%= operation %>_request_response_pair][:all] = []
73
+
74
+ response_bundles = target_pairs.map { |pair| pair[:response_bundle] }
75
+ save_bundles_and_entries_to_scratch(response_bundles)
76
+
77
+ target_pairs.each do |pair|
78
+ pair => {request_bundle:, response_bundle:}
79
+ assert_resource_type(:bundle, resource: response_bundle)
80
+
81
+ perform_response_validation(
82
+ response_bundle,
83
+ '<%= profile_url %>',
84
+ '<%= profile_version %>',
85
+ request_type,
86
+ request_bundle
87
+ )
88
+ end
89
+
90
+ validation_error_messages.each do |msg|
91
+ messages << { type: 'error', message: msg }
92
+ end
93
+ msg = 'Bundle response returned and/or entry resources are not conformant. Check messages for issues found.'
94
+ assert validation_error_messages.blank?, msg<% end %>
95
+ end
96
+ end
97
+ end
98
+ end
@@ -6,42 +6,23 @@ module DaVinciPASTestKit
6
6
  class ValidationTestGenerator
7
7
  class << self
8
8
  def generate(ig_metadata, base_output_dir)
9
- ['server', 'client'].each do |system|
10
- if system == 'server'
11
- ig_metadata.bundle_groups.each do |group|
12
- new(group, system, base_output_dir:).generate
13
- end
14
- else
15
- ig_metadata.bundle_groups.each do |group|
16
- case group.profile_name
17
- when 'PAS Request Bundle'
18
- new(group, system, base_output_dir:).generate
19
- when 'PAS Inquiry Request Bundle'
20
- new(group, system, 'pended_inquiry', base_output_dir:).generate
21
- when 'PAS Response Bundle'
22
- ['denial', 'pended'].each do |workflow|
23
- new(group, system, workflow, base_output_dir:).generate
24
- end
25
- end
26
- end
27
- end
9
+ ig_metadata.bundle_groups.each do |group|
10
+ new(group, base_output_dir:).generate
28
11
  end
29
12
  end
30
13
  end
31
14
 
32
- attr_accessor :group_metadata, :medication_request_metadata, :base_output_dir, :system, :workflow
15
+ attr_accessor :group_metadata, :medication_request_metadata, :base_output_dir, :workflow
33
16
 
34
- def initialize(group_metadata, system, workflow = nil, medication_request_metadata = nil, base_output_dir:)
17
+ def initialize(group_metadata, workflow = nil, medication_request_metadata = nil, base_output_dir:)
35
18
  self.group_metadata = group_metadata
36
- self.system = system
37
19
  self.workflow = workflow
38
20
  self.medication_request_metadata = medication_request_metadata
39
21
  self.base_output_dir = base_output_dir
40
22
  end
41
23
 
42
24
  def template
43
- temp = system == 'server' ? 'validation.rb.erb' : 'validation_client.rb.erb'
44
- @template ||= File.read(File.join(__dir__, 'templates', temp))
25
+ @template ||= File.read(File.join(__dir__, 'templates', 'validation.rb.erb'))
45
26
  end
46
27
 
47
28
  def output
@@ -61,8 +42,6 @@ module DaVinciPASTestKit
61
42
  end
62
43
 
63
44
  def directory_name
64
- return 'client_tests' if system == 'client'
65
-
66
45
  Naming.snake_case_for_profile(medication_request_metadata || group_metadata)
67
46
  end
68
47
 
@@ -87,12 +66,12 @@ module DaVinciPASTestKit
87
66
  end
88
67
 
89
68
  def test_id
90
- pref = "pas_#{system}_#{group_metadata.reformatted_version}_#{formatted_workflow}".delete_suffix('_')
69
+ pref = "pas_server_#{group_metadata.reformatted_version}_#{formatted_workflow}".delete_suffix('_')
91
70
  "#{pref}_#{profile_identifier}_validation_test"
92
71
  end
93
72
 
94
73
  def class_name
95
- pref = "#{system.capitalize}#{formatted_workflow.camelize}"
74
+ pref = "Server#{formatted_workflow.camelize}"
96
75
  "#{pref}#{Naming.upper_camel_case_for_profile(group_metadata)}ValidationTest"
97
76
  end
98
77
 
@@ -135,14 +114,23 @@ module DaVinciPASTestKit
135
114
  end
136
115
 
137
116
  def user_input?
138
- (system == 'server' && request_type.include?('request')) ||
139
- (system == 'client' && request_type.include?('response'))
117
+ request_type.include?('request')
140
118
  end
141
119
 
142
120
  def description
143
121
  <<~DESCRIPTION
144
122
  #{description_user_input_validation if user_input?}
145
- #{system == 'server' ? description_intro_server : description_intro_client}
123
+
124
+ This test validates the conformity of the
125
+ #{request_type.include?('request') ? 'user input' : "server's response"} to the
126
+ [#{profile_name}](#{profile_url})#{' '}
127
+ structure#{request_type.include?('request') ? ', ensuring subsequent tests can accurately simulate content.' : '.'}
128
+
129
+ It also checks that other conformance requirements defined in the [PAS Formal
130
+ Specification](https://hl7.org/fhir/us/davinci-pas/STU2/specification.html),
131
+ such as the presence of all referenced instances within the bundle and the
132
+ conformance of those instances to the appropriate profiles, are met.
133
+
146
134
  It verifies the presence of mandatory elements and that elements with
147
135
  required bindings contain appropriate values. CodeableConcept element
148
136
  bindings will fail if none of their codings have a code/system belonging
@@ -162,31 +150,6 @@ module DaVinciPASTestKit
162
150
  DESCRIPTION
163
151
  end
164
152
 
165
- def description_intro_server
166
- <<~GENERIC_INTRO
167
- This test validates the conformity of the
168
- #{request_type.include?('request') ? 'user input' : "server's response"} to the
169
- [#{profile_name}](#{profile_url}) structure#{request_type.include?('request') ? ', ensuring subsequent tests can accurately simulate content.' : '.'}
170
-
171
- It also checks that other conformance requirements defined in the [PAS Formal
172
- Specification](https://hl7.org/fhir/us/davinci-pas/STU2/specification.html),
173
- such as the presence of all referenced instances within the bundle and the
174
- conformance of those instances to the appropriate profiles, are met.
175
- GENERIC_INTRO
176
- end
177
-
178
- def description_intro_client
179
- <<~GENERIC_INTRO
180
- This test validates the conformity of the
181
- #{request_type.include?('response') ? 'user input' : "client's request"} to the
182
- [#{profile_name}](#{profile_url}) structure.
183
- It also checks that other conformance requirements defined in the [PAS Formal
184
- Specification](https://hl7.org/fhir/us/davinci-pas/STU2/specification.html),
185
- such as the presence of all referenced instances within the bundle and the
186
- conformance of those instances to the appropriate profiles, are met.
187
- GENERIC_INTRO
188
- end
189
-
190
153
  def description_user_input_validation
191
154
  <<~USER_INPUT_INTRO
192
155
  **USER INPUT VALIDATION**: This test validates input provided by the user instead of the system under test.
@@ -17,7 +17,10 @@ module DaVinciPASTestKit
17
17
  end
18
18
 
19
19
  def bound_systems(the_element)
20
- value_set(the_element)&.compose&.include&.reject { |code| code.concept.nil? }
20
+ vs = value_set(the_element)
21
+ return unless vs.present?
22
+
23
+ vs.compose&.include&.reject { |code| code.concept.nil? }
21
24
  end
22
25
 
23
26
  def values_from_value_set_binding(the_element)
@@ -38,7 +38,7 @@ module DaVinciPASTestKit
38
38
  end
39
39
 
40
40
  def load_ig_package
41
- FHIR.logger = Logger.new('/dev/null')
41
+ FHIR.logger = Logger.new(File::NULL)
42
42
  self.ig_resources = IGLoader.new(ig_file_name).load
43
43
  end
44
44
 
@@ -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