subscriptions_test_kit 0.9.2 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno_requirements_tools/tasks/collect_requirements.rb +51 -50
  3. data/lib/inferno_requirements_tools/tasks/requirements_coverage.rb +22 -19
  4. data/lib/subscriptions_test_kit/common/notification_conformance_verification.rb +11 -12
  5. data/lib/subscriptions_test_kit/common/subscription_conformance_verification.rb +14 -2
  6. data/lib/subscriptions_test_kit/docs/samples/Subscription_empty.json +1 -1
  7. data/lib/subscriptions_test_kit/docs/samples/Subscription_full-resource.json +1 -1
  8. data/lib/subscriptions_test_kit/docs/samples/Subscription_id-only.json +1 -1
  9. data/lib/subscriptions_test_kit/docs/subscriptions_r5_backport_r4_client_suite_description.md +4 -1
  10. data/lib/subscriptions_test_kit/docs/subscriptions_r5_backport_r4_server_suite_description.md +4 -1
  11. data/lib/subscriptions_test_kit/endpoints/subscription_create_endpoint.rb +7 -3
  12. data/lib/subscriptions_test_kit/endpoints/subscription_status_endpoint.rb +19 -13
  13. data/lib/subscriptions_test_kit/jobs/send_subscription_notifications.rb +7 -2
  14. data/lib/subscriptions_test_kit/requirements/generated/subscriptions-test-kit_requirements_coverage.csv +51 -51
  15. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/common/subscription_simulation_utils.rb +47 -16
  16. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification/notification_input_payload_verification_test.rb +15 -8
  17. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification/notification_input_verification_test.rb +8 -5
  18. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification/processing_attestation_test.rb +1 -1
  19. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification/subscription_verification_test.rb +3 -2
  20. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/conformance_verification_group.rb +1 -1
  21. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/interaction_test.rb +15 -11
  22. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/interaction_verification/event_notification_verification_test.rb +6 -5
  23. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/interaction_verification/handshake_notification_verification_test.rb +5 -5
  24. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client/workflow/interaction_verification_group.rb +1 -1
  25. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_client_suite.rb +5 -3
  26. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/capability_statement/cs_conformance_test.rb +1 -1
  27. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/capability_statement/topic_discovery_test.rb +3 -3
  28. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/capability_statement_group.rb +3 -3
  29. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/interaction/creation_response_conformance_test.rb +1 -1
  30. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/interaction/subscription_conformance_test.rb +11 -14
  31. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/interaction_group.rb +3 -3
  32. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/subscription_creation.rb +10 -1
  33. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/subscription_status_operation.rb +3 -2
  34. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/event_notification/empty_content_group.rb +3 -2
  35. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/event_notification/full_resource_content/full_resource_conformance_test.rb +3 -4
  36. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/event_notification/full_resource_content_group.rb +3 -2
  37. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/event_notification/id_only_content/id_only_conformance_test.rb +3 -4
  38. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/event_notification/id_only_content_group.rb +3 -2
  39. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/event_notification_group.rb +2 -2
  40. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/handshake_heartbeat/handshake_conformance_test.rb +7 -6
  41. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/handshake_heartbeat/heartbeat_conformance_test.rb +4 -2
  42. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/status_operation/status_invocation_test.rb +2 -2
  43. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/status_operation_group.rb +1 -1
  44. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_channel_endpoint_test.rb +65 -0
  45. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_channel_payload_combo_test.rb +76 -0
  46. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_channel_type_test.rb +68 -0
  47. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_cross_version_extension_test.rb +59 -0
  48. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_filter_test.rb +66 -0
  49. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_payload_type_test.rb +66 -0
  50. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscription_topic_test.rb +64 -0
  51. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection_group.rb +14 -2
  52. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server_suite.rb +3 -2
  53. data/lib/subscriptions_test_kit/version.rb +1 -3
  54. metadata +11 -75
  55. data/lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/subscription_rejection/reject_subscriptions_test.rb +0 -181
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4aa07a6d63da9348618f7c9d28443c743ff8669b25e5304c2d572370d7eb457c
4
- data.tar.gz: 28ca4154cb6b064fd6c7d5cb7dd47f9b6c68a5eef051214e091152517971979e
3
+ metadata.gz: 5a807454f459add0f7fd19401c70a5a3c190c5003c375c0b4eefe1e4103c8410
4
+ data.tar.gz: e284a813940aed69d0cfb3dffef7f1ab034c2fbf799182fe5b200d7edcb1285d
5
5
  SHA512:
6
- metadata.gz: 756cebdf92654e1115f752c127e68c41052259087c781a665f9f286f4eaecb48bd10996cba20d6e0cfce1c287e3779be0a401e6a8f297b94bc40952e9686c013
7
- data.tar.gz: d8146e70aae224a33f534f05b54282d96bdc62742d5710d3f6e30260d480ec52efd9396a98090800756611640cf66bbe695b7322c1f650eb47be3fb5e729afb3
6
+ metadata.gz: 248c63937da747106d20cbb41dbfd80728de783757a322160882d651ef7f43c30b4192fdf83f036803ef8e50eae082348284fcd30c3e537ae99385df080baf68
7
+ data.tar.gz: 89d5e860a4fa25b362d5a5d55054ec729dfd4395aa09d453bf71123c7b381120a0510509ad6fea2803665931ccd515c844f652442c1c6b8965b58a4fc6f6a0d6
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'CSV'
3
+ require 'csv'
4
4
  require 'roo'
5
5
 
6
6
  module InfernoRequirementsTools
@@ -52,22 +52,26 @@ module InfernoRequirementsTools
52
52
  PLANNED_NOT_TESTED_OUTPUT_FILE =
53
53
  File.join('lib', TEST_KIT_CODE_FOLDER, 'requirements', PLANNED_NOT_TESTED_OUTPUT_FILE_NAME).freeze
54
54
 
55
- def input_file_map
56
- @input_file_map ||= {}
55
+ def available_input_worksheets
56
+ @available_input_worksheets ||= Dir.glob(File.join(@input_directory, '*.xlsx')).reject { |f| f.include?('~$') }
57
57
  end
58
58
 
59
- def input_rows
60
- @input_rows ||= {}
61
- end
62
-
63
- def input_rows_for_set(req_set_id)
64
- input_rows[req_set_id] = extract_input_rows_for_set(req_set_id) unless input_rows.has_key?(req_set_id)
65
- input_rows[req_set_id]
66
- end
67
-
68
- def extract_input_rows_for_set(req_set_id)
69
- CSV.parse(Roo::Spreadsheet.open(input_file_map[req_set_id]).sheet('Requirements').to_csv, headers: true).map do |row|
70
- row.to_h.slice(*INPUT_HEADERS)
59
+ # Of the form:
60
+ # {
61
+ # req_set_id_1: [row1, row2, row 3, ...],
62
+ # req_set_id_2: [row1, row2, row 3, ...]
63
+ # }
64
+ def input_requirement_sets
65
+ @input_requirement_sets ||= INPUT_SETS.each_with_object({}) do |req_set_id, hash|
66
+ req_set_file = available_input_worksheets.find { |worksheet_file| worksheet_file.include?(req_set_id) }
67
+
68
+ hash[req_set_id] =
69
+ unless req_set_file.nil?
70
+ CSV.parse(Roo::Spreadsheet.open(req_set_file).sheet('Requirements').to_csv,
71
+ headers: true).map do |row|
72
+ row.to_h.slice(*INPUT_HEADERS)
73
+ end
74
+ end
71
75
  end
72
76
  end
73
77
 
@@ -76,12 +80,11 @@ module InfernoRequirementsTools
76
80
  CSV.generate(+"\xEF\xBB\xBF") do |csv| # start with an unnecessary BOM to make viewing in excel easier
77
81
  csv << REQUIREMENTS_OUTPUT_HEADERS
78
82
 
79
- INPUT_SETS.each do |req_set_id|
80
- input_rows = input_rows_for_set(req_set_id)
81
- input_rows.each do |row| # NOTE: use row order from source file
82
- row['Req Set'] = req_set_id
83
-
84
- csv << REQUIREMENTS_OUTPUT_HEADERS.map { |header| row.key?(header) ? row[header] : row["#{header}*"]}
83
+ input_requirement_sets.each do |req_set_id, input_rows|
84
+ input_rows.each do |input_row| # NOTE: use row order from source file
85
+ csv << REQUIREMENTS_OUTPUT_HEADERS.map do |header|
86
+ header == 'Req Set' ? req_set_id : input_row[header] || input_row["#{header}*"]
87
+ end
85
88
  end
86
89
  end
87
90
  end
@@ -96,19 +99,13 @@ module InfernoRequirementsTools
96
99
  CSV.generate(+"\xEF\xBB\xBF") do |csv| # start with an unnecessary BOM to make viewing in excel easier
97
100
  csv << PLANNED_NOT_TESTED_OUTPUT_HEADERS
98
101
 
99
- INPUT_SETS.each do |req_set_id|
100
- input_rows = input_rows_for_set(req_set_id)
101
- input_rows.each do |row| # note: use row order from source file
102
- not_verifiable = row['Verifiable?']&.downcase == 'no' || row['Verifiable?']&.downcase == 'false'
103
- not_tested = row['Planning To Test?']&.downcase == 'no' || row['Planning To Test?']&.downcase == 'false'
104
- next unless not_verifiable || not_tested
105
-
106
- csv << [
107
- req_set_id,
108
- row['ID*'],
109
- not_verifiable ? 'Not Verifiable' : 'Not Tested',
110
- not_verifiable ? row['Verifiability Details'] : row['Planning To Test Details']
111
- ]
102
+ input_requirement_sets.each do |req_set_id, input_rows|
103
+ input_rows.each do |row|
104
+ if spreadsheet_value_falsy?(row['Verifiable?'])
105
+ csv << [req_set_id, row['ID*'], 'Not Verifiable', row['Verifiability Details']]
106
+ elsif spreadsheet_value_falsy?(row['Planning To Test?'])
107
+ csv << [req_set_id, row['ID*'], 'Not Tested', row['Planning To Test Details']]
108
+ end
112
109
  end
113
110
  end
114
111
  end
@@ -118,22 +115,9 @@ module InfernoRequirementsTools
118
115
  @old_planned_not_tested_csv ||= File.read(PLANNED_NOT_TESTED_OUTPUT_FILE)
119
116
  end
120
117
 
121
- def check_for_req_set_files(input_directory)
122
- available_worksheets = Dir.glob(File.join(input_directory, '*.xlsx')).reject { |f| f.include?('~$') }
123
-
124
- INPUT_SETS.each do |req_set_id|
125
- req_set_file = available_worksheets&.find { |worksheet_file| worksheet_file.include?(req_set_id) }
126
-
127
- if req_set_file&.empty?
128
- puts "Could not find input file for set #{req_set_id} in directory #{input_directory}. Aborting requirements collection..."
129
- exit(1)
130
- end
131
- input_file_map[req_set_id] = req_set_file
132
- end
133
- end
134
-
135
118
  def run(input_directory)
136
- check_for_req_set_files(input_directory)
119
+ @input_directory = input_directory
120
+ check_presence_of_input_files
137
121
 
138
122
  update_requirements =
139
123
  if File.exist?(REQUIREMENTS_OUTPUT_FILE)
@@ -177,7 +161,8 @@ module InfernoRequirementsTools
177
161
  end
178
162
 
179
163
  def run_check(input_directory)
180
- check_for_req_set_files(input_directory)
164
+ @input_directory = input_directory
165
+ check_presence_of_input_files
181
166
 
182
167
  requirements_ok =
183
168
  if File.exist?(REQUIREMENTS_OUTPUT_FILE)
@@ -217,6 +202,22 @@ module InfernoRequirementsTools
217
202
  MESSAGE
218
203
  exit(1)
219
204
  end
205
+
206
+ def check_presence_of_input_files
207
+ input_requirement_sets.each do |req_set_id, rows|
208
+ next unless rows.nil?
209
+
210
+ puts %(
211
+ Could not find input file for set #{req_set_id} in directory #{input_directory}. Aborting requirements
212
+ collection..."
213
+ )
214
+ exit(1)
215
+ end
216
+ end
217
+
218
+ def spreadsheet_value_falsy?(str)
219
+ str&.downcase == 'no' || str&.downcase == 'false'
220
+ end
220
221
  end
221
222
  end
222
223
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'CSV'
3
+ require 'csv'
4
4
  require_relative '../ext/inferno_core/runnable'
5
5
 
6
6
  module InfernoRequirementsTools
@@ -51,12 +51,15 @@ module InfernoRequirementsTools
51
51
  INPUT_FILE = File.join('lib', TEST_KIT_CODE_FOLDER, 'requirements', INPUT_FILE_NAME).freeze
52
52
  NOT_TESTED_FILE_NAME = "#{TEST_KIT_ID}_out_of_scope_requirements.csv".freeze
53
53
  NOT_TESTED_FILE = File.join('lib', TEST_KIT_CODE_FOLDER, 'requirements', NOT_TESTED_FILE_NAME).freeze
54
+ OUTPUT_HEADERS = INPUT_HEADERS + TEST_SUITES.flat_map do |suite|
55
+ ["#{suite.title} #{SHORT_ID_HEADER}", "#{suite.title} #{FULL_ID_HEADER}"]
56
+ end
54
57
  OUTPUT_FILE_NAME = "#{TEST_KIT_ID}_requirements_coverage.csv".freeze
55
58
  OUTPUT_FILE = File.join('lib', TEST_KIT_CODE_FOLDER, 'requirements', 'generated', OUTPUT_FILE_NAME).freeze
56
59
 
57
60
  def input_rows
58
61
  @input_rows ||=
59
- CSV.parse(File.open(INPUT_FILE, "r:bom|utf-8"), headers: true).map do |row|
62
+ CSV.parse(File.open(INPUT_FILE, 'r:bom|utf-8'), headers: true).map do |row|
60
63
  row.to_h.slice(*INPUT_HEADERS)
61
64
  end
62
65
  end
@@ -66,12 +69,12 @@ module InfernoRequirementsTools
66
69
  end
67
70
 
68
71
  def load_not_tested_requirements
69
- return {} unless File.exists?(NOT_TESTED_FILE)
72
+ return {} unless File.exist?(NOT_TESTED_FILE)
70
73
 
71
74
  not_tested_requirements = {}
72
- CSV.parse(File.open(NOT_TESTED_FILE, "r:bom|utf-8"), headers: true).each do |row|
75
+ CSV.parse(File.open(NOT_TESTED_FILE, 'r:bom|utf-8'), headers: true).each do |row|
73
76
  row_hash = row.to_h
74
- not_tested_requirements[ "#{row_hash['Req Set']}@#{row_hash['ID']}"] = row_hash
77
+ not_tested_requirements["#{row_hash['Req Set']}@#{row_hash['ID']}"] = row_hash
75
78
  end
76
79
 
77
80
  not_tested_requirements
@@ -95,26 +98,24 @@ module InfernoRequirementsTools
95
98
  end
96
99
  end
97
100
 
101
+ # rubocop:disable Metrics/CyclomaticComplexity
98
102
  def new_csv
99
103
  @new_csv ||=
100
104
  CSV.generate(+"\xEF\xBB\xBF") do |csv|
101
- output_headers = TEST_SUITES.each_with_object(INPUT_HEADERS.dup) do |suite, headers|
102
- headers << "#{suite.title} #{SHORT_ID_HEADER}"
103
- headers << "#{suite.title} #{FULL_ID_HEADER}"
104
- end
105
-
106
- csv << output_headers
107
- input_rows.each do |row| # note: use row order from source file
105
+ csv << OUTPUT_HEADERS
106
+ input_rows.each do |row| # NOTE: use row order from source file
108
107
  next if row['Conformance'] == 'DEPRECATED' # filter out deprecated rows
109
- row_actor = row['Actor']
108
+
110
109
  TEST_SUITES.each do |suite|
111
110
  suite_actor = SUITE_ID_TO_ACTOR_MAP[suite.id]
112
- if row_actor&.include?(suite_actor)
111
+ if row['Actor']&.include?(suite_actor)
113
112
  set_and_req_id = "#{row['Req Set']}@#{row['ID']}"
114
- suite_requirement_items = inferno_requirements_map[set_and_req_id]&.filter { |item| item[:suite_id] == suite.id}
113
+ suite_requirement_items = inferno_requirements_map[set_and_req_id]&.filter do |item|
114
+ item[:suite_id] == suite.id
115
+ end
115
116
  short_ids = suite_requirement_items&.map { |item| item[:short_id] }
116
117
  full_ids = suite_requirement_items&.map { |item| item[:full_id] }
117
- if short_ids.blank? && not_tested_requirements_map.has_key?(set_and_req_id)
118
+ if short_ids.blank? && not_tested_requirements_map.key?(set_and_req_id)
118
119
  row["#{suite.title} #{SHORT_ID_HEADER}"] = 'Not Tested'
119
120
  row["#{suite.title} #{FULL_ID_HEADER}"] = 'Not Tested'
120
121
  else
@@ -131,6 +132,7 @@ module InfernoRequirementsTools
131
132
  end
132
133
  end
133
134
  end
135
+ # rubocop:enable Metrics/CyclomaticComplexity
134
136
 
135
137
  def input_requirement_ids
136
138
  @input_requirement_ids ||= input_rows.map { |row| "#{row['Req Set']}@#{row['ID']}" }
@@ -138,9 +140,7 @@ module InfernoRequirementsTools
138
140
 
139
141
  # The requirements present in Inferno that aren't in the input spreadsheet
140
142
  def unmatched_requirements_map
141
- @unmatched_requirements_map ||= inferno_requirements_map.filter do |requirement_id, _|
142
- !input_requirement_ids.include?(requirement_id)
143
- end
143
+ @unmatched_requirements_map ||= inferno_requirements_map.except(*input_requirement_ids)
144
144
  end
145
145
 
146
146
  def old_csv
@@ -233,6 +233,8 @@ module InfernoRequirementsTools
233
233
  # ---------------+------------+----------
234
234
  # req-id-1 | short-id-1 | full-id-1
235
235
  # req-id-2 | short-id-2 | full-id-2
236
+ #
237
+ # rubocop:disable Metrics/CyclomaticComplexity
236
238
  def output_requirements_map_table(requirements_map)
237
239
  headers = %w[requirement_id short_id full_id]
238
240
  col_widths = headers.map(&:length)
@@ -258,6 +260,7 @@ module InfernoRequirementsTools
258
260
  end
259
261
  puts
260
262
  end
263
+ # rubocop:enable Metrics/CyclomaticComplexity
261
264
  end
262
265
  end
263
266
  end
@@ -32,9 +32,11 @@ module SubscriptionsTestKit
32
32
 
33
33
  unless subscription_status_entry.request.present? &&
34
34
  (
35
- subscription_id ?
36
- subscription_status_entry.request.url.end_with?("Subscription/#{subscription_id}/$status") :
35
+ if subscription_id
36
+ subscription_status_entry.request.url.end_with?("Subscription/#{subscription_id}/$status")
37
+ else
37
38
  subscription_status_entry.request.url.match?(%r{Subscription/[^/]+/\$status\z})
39
+ end
38
40
  )
39
41
 
40
42
  add_message('error',
@@ -148,8 +150,14 @@ module SubscriptionsTestKit
148
150
  end
149
151
 
150
152
  def check_bundle_entry_reference(bundle_entries, reference)
153
+ check_full_url = reference.start_with?('urn:')
154
+
151
155
  referenced_entry = bundle_entries.find do |entry|
152
- reference.include?("#{entry.resource.resourceType}/#{entry.resource.id}")
156
+ if check_full_url
157
+ reference == entry.fullUrl
158
+ else
159
+ reference.include?("#{entry.resource.resourceType}/#{entry.resource.id}")
160
+ end
153
161
  end
154
162
  referenced_entry.present?
155
163
  end
@@ -224,15 +232,6 @@ module SubscriptionsTestKit
224
232
  resource in the entry.resource element.))
225
233
  end
226
234
 
227
- def subscription_criteria(subscription)
228
- return unless subscription['_criteria']
229
-
230
- criteria_extension = subscription['_criteria']['extension'].find do |ext|
231
- ext['url'].ends_with?('/backport-filter-criteria')
232
- end
233
- criteria_extension['valueString'].split('?').first
234
- end
235
-
236
235
  def empty_event_notification_verification(notification_bundle)
237
236
  assert_valid_json(notification_bundle)
238
237
  bundle = FHIR.from_contents(notification_bundle)
@@ -37,6 +37,13 @@ module SubscriptionsTestKit
37
37
  end
38
38
  end
39
39
 
40
+ def valid_url?(url)
41
+ uri = URI.parse(url)
42
+ uri.is_a?(URI::HTTP) && !uri.host.nil?
43
+ rescue URI::InvalidURIError
44
+ false
45
+ end
46
+
40
47
  def subscription_verification(subscription_resource)
41
48
  assert_valid_json(subscription_resource)
42
49
  subscription = JSON.parse(subscription_resource)
@@ -46,6 +53,11 @@ module SubscriptionsTestKit
46
53
  The `type` field on the Subscription resource must be set to `rest-hook`, the `#{subscription_channel['type']}`
47
54
  channel type is unsupported.))
48
55
 
56
+ unless subscription['criteria'].present? && valid_url?(subscription['criteria'])
57
+ add_message('error', %(
58
+ 'The `criteria` field SHALL be populated and contain the canonical URL for the Subscription Topic.'
59
+ ))
60
+ end
49
61
  subscription_resource = FHIR.from_contents(subscription.to_json)
50
62
  assert_resource_type('Subscription', resource: subscription_resource)
51
63
  assert_valid_resource(resource: subscription_resource,
@@ -77,8 +89,8 @@ module SubscriptionsTestKit
77
89
  Added the Authorization header field with a Bearer token set to #{access_token} to the `header` field on the
78
90
  Subscription resource in order to connect successfully with the Inferno subscription channel.
79
91
  ))
80
- subscription['header'] = [] unless subscription['header'].present?
81
- subscription['header'].append("Authorization: Bearer #{access_token}")
92
+ subscription_channel['header'] = [] unless subscription_channel['header'].present?
93
+ subscription_channel['header'].append("Authorization: Bearer #{access_token}")
82
94
  end
83
95
  subscription['channel'] = subscription_channel
84
96
  subscription
@@ -11,7 +11,7 @@
11
11
  "_criteria" : {
12
12
  "extension" : [{
13
13
  "url" : "http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-filter-criteria",
14
- "valueString" : "Encounter?patient=Patient/123"
14
+ "valueString" : "Encounter.patient=Patient/123"
15
15
  }]
16
16
  },
17
17
  "channel" : {
@@ -11,7 +11,7 @@
11
11
  "_criteria" : {
12
12
  "extension" : [{
13
13
  "url" : "http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-filter-criteria",
14
- "valueString" : "Encounter?patient=Patient/123"
14
+ "valueString" : "Encounter.patient=Patient/123"
15
15
  }]
16
16
  },
17
17
  "channel" : {
@@ -12,7 +12,7 @@
12
12
  "_criteria" : {
13
13
  "extension" : [{
14
14
  "url" : "http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-filter-criteria",
15
- "valueString" : "Encounter?patient=Patient/123"
15
+ "valueString" : "Encounter.patient=Patient/123"
16
16
  }]
17
17
  },
18
18
  "channel" : {
@@ -117,4 +117,7 @@ Specific limitations to highlight include
117
117
  [provide feedback](https://github.com/inferno-framework/subscriptions-test-kit/issues) to that effect.
118
118
  - Inferno does not test delivery error handling and recovery scenarios, including
119
119
  the optional `$events` API and event numbering details.
120
- - Inferno does not support sending heartbeat notifications.
120
+ - Inferno does not support sending heartbeat notifications.
121
+ - Inferno does not verify that the shape and content of notifications are appropriate for the triggering
122
+ Subscription because those details, e.g., the resource types that can be a focus of the notification,
123
+ are defined within the SubscriptionTopic which is not available in FHIR R4.
@@ -167,4 +167,7 @@ Specific limitations to highlight include
167
167
  If there is a channel type that you would like to see verified, please
168
168
  [provide feedback](https://github.com/inferno-framework/subscriptions-test-kit/issues) to that effect.
169
169
  - Inferno does not test delivery error handling and recovery scenarios, including
170
- the optional `$events` API and event numbering details.
170
+ the optional `$events` API and event numbering details.
171
+ - Inferno does not verify that the shape and content of notifications are appropriate for the triggering
172
+ Subscription because those details, e.g., the resource types that can be a focus of the notification,
173
+ are defined within the SubscriptionTopic which is not available in FHIR R4.
@@ -32,7 +32,7 @@ module SubscriptionsTestKit
32
32
  existing_subscription_request = requests.find { |r| r.status == 201 }
33
33
  if existing_subscription_request.present?
34
34
  subscription_hash = JSON.parse(existing_subscription_request.response_body)
35
- error_text = 'Inferno only supports one subscription per test run. Subscription already created with '\
35
+ error_text = 'Inferno only supports one subscription per test run. Subscription already created with ' \
36
36
  "ID #{subscription_hash['id']}"
37
37
  response.body = operation_outcome('error', 'business-rule', error_text).to_json
38
38
  return
@@ -74,10 +74,14 @@ module SubscriptionsTestKit
74
74
  return operation_outcome('error', 'value', 'channel.endpoint is not recognized as a conformant URL')
75
75
  end
76
76
 
77
- heartbeat_period = subscription.channel&.extension&.find do |e|
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|
78
83
  e.url == 'http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-heartbeat-period'
79
84
  end
80
- operation_outcome('error', 'not-supported', 'heartbeatPeriod is not supported') unless heartbeat_period.nil?
81
85
  end
82
86
 
83
87
  def valid_url?(url)
@@ -30,28 +30,30 @@ module SubscriptionsTestKit
30
30
  return
31
31
  end
32
32
 
33
- id_params = params&.parameter&.filter { |p| p.name == 'id' }
34
-
35
- if id_params&.any? && id_params&.none? { |p| p.valueString == subscription.id }
33
+ unless subscription_params_match?(params)
36
34
  not_found
37
35
  return
38
- else
39
- status_params = params&.parameter&.filter { |p| p.name == 'status' }
40
- subscription_status = determine_subscription_status_code(subscription.id)
41
- if status_params&.any? && status_params.none? { |p| p.valueString == subscription_status }
42
- not_found
43
- return
44
- end
45
36
  end
46
37
  end
47
38
 
48
39
  notification_json = notification_bundle_input(result)
49
40
  subscription_url = "#{base_subscription_url}/#{subscription.id}"
41
+ subscription_topic = subscription.criteria
50
42
  status_code = determine_subscription_status_code(subscription_id)
51
43
  event_count = determine_event_count(test_run.test_session_id)
52
44
  response.status = 200
53
- response.body = derive_status_bundle(notification_json, subscription_url, status_code, event_count,
54
- request.url).to_json
45
+ response.body = derive_status_bundle(notification_json, subscription_url, subscription_topic, status_code,
46
+ event_count, request.url).to_json
47
+ end
48
+
49
+ def subscription_params_match?(params)
50
+ id_params = find_params(params, 'id')
51
+
52
+ return false if id_params&.any? && id_params&.none? { |p| p.valueString == subscription.id }
53
+
54
+ status_params = find_params(params, 'status')
55
+ subscription_status = determine_subscription_status_code(subscription.id)
56
+ status_params.nil? || status_params.none? || status_params.any { p.valueString == subscription_status }
55
57
  end
56
58
 
57
59
  def tags
@@ -63,8 +65,12 @@ module SubscriptionsTestKit
63
65
  response.body = operation_outcome('error', 'not-found').to_json
64
66
  end
65
67
 
68
+ def find_params(params, name)
69
+ params&.parameter&.filter { |p| p.name == name }
70
+ end
71
+
66
72
  def base_subscription_url
67
- request.url.sub(%r{(#{Regexp.escape(FHIR_SUBSCRIPTION_PATH)}).*}, '\1')
73
+ request.url.sub(/(#{Regexp.escape(FHIR_SUBSCRIPTION_PATH)}).*/, '\1')
68
74
  end
69
75
  end
70
76
  end
@@ -74,6 +74,10 @@ module SubscriptionsTestKit
74
74
  @authorization_header ||= @bearer_token.present? ? { 'Authorization' => "Bearer #{@bearer_token}" } : {}
75
75
  end
76
76
 
77
+ def subscription_topic
78
+ @subscription_topic ||= subscription&.criteria
79
+ end
80
+
77
81
  def test_still_waiting?
78
82
  results_repo.find_waiting_result(test_run_id: @test_run_id)
79
83
  end
@@ -83,14 +87,15 @@ module SubscriptionsTestKit
83
87
  end
84
88
 
85
89
  def send_handshake_notification
86
- handshake_json = derive_handshake_notification(@notification_json, @subscription_url).to_json
90
+ handshake_json = derive_handshake_notification(@notification_json, @subscription_url,
91
+ subscription_topic).to_json
87
92
  response = send_notification(handshake_json)
88
93
  persist_notification_request(response, [REST_HOOK_HANDSHAKE_NOTIFICATION_TAG])
89
94
  resume_inferno_test unless response.status == 200
90
95
  end
91
96
 
92
97
  def send_event_notification
93
- event_json = derive_event_notification(@notification_json, @subscription_url, 1).to_json
98
+ event_json = derive_event_notification(@notification_json, @subscription_url, subscription_topic, 1).to_json
94
99
  response = send_notification(event_json)
95
100
  persist_notification_request(response, [REST_HOOK_EVENT_NOTIFICATION_TAG])
96
101
  end