onc_certification_g10_test_kit 5.4.2 → 6.0.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/onc_certification_g10_test_kit/base_token_refresh_stu2_group.rb +49 -0
  3. data/lib/onc_certification_g10_test_kit/multi_patient_api_stu1.rb +13 -8
  4. data/lib/onc_certification_g10_test_kit/multi_patient_api_stu2.rb +12 -7
  5. data/lib/onc_certification_g10_test_kit/onc_program_procedure.yml +448 -468
  6. data/lib/onc_certification_g10_test_kit/short_id_map.yml +227 -17
  7. data/lib/onc_certification_g10_test_kit/single_patient_api_group.rb +6 -1
  8. data/lib/onc_certification_g10_test_kit/single_patient_us_core_4_api_group.rb +6 -1
  9. data/lib/onc_certification_g10_test_kit/single_patient_us_core_6_api_group.rb +8 -5
  10. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +16 -17
  11. data/lib/onc_certification_g10_test_kit/smart_asymmetric_launch_group.rb +194 -0
  12. data/lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group.rb +2 -4
  13. data/lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2.rb +7 -6
  14. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +23 -7
  15. data/lib/onc_certification_g10_test_kit/smart_fine_grained_scopes_group.rb +188 -0
  16. data/lib/onc_certification_g10_test_kit/smart_granular_scope_selection_group.rb +150 -0
  17. data/lib/onc_certification_g10_test_kit/smart_granular_scope_selection_test.rb +53 -0
  18. data/lib/onc_certification_g10_test_kit/smart_invalid_pkce_group.rb +6 -7
  19. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +8 -10
  20. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group_stu2.rb +7 -9
  21. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +5 -3
  22. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +14 -16
  23. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group_stu2.rb +28 -4
  24. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +34 -25
  25. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +20 -9
  26. data/lib/onc_certification_g10_test_kit/smart_v1_scopes_group.rb +241 -0
  27. data/lib/onc_certification_g10_test_kit/tasks/generate_matrix.rb +75 -51
  28. data/lib/onc_certification_g10_test_kit/token_introspection_group.rb +110 -0
  29. data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +1 -1
  30. data/lib/onc_certification_g10_test_kit/version.rb +1 -1
  31. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +57 -37
  32. data/lib/onc_certification_g10_test_kit.rb +84 -18
  33. metadata +13 -6
@@ -1,7 +1,7 @@
1
1
  module ONCCertificationG10TestKit
2
2
  class SMARTPublicStandaloneLaunchGroupSTU2 < SMARTAppLaunch::StandaloneLaunchGroupSTU2
3
- title 'SMART Public Client Standalone Launch with OpenID Connect'
4
- short_title 'SMART Public Client Launch'
3
+ title 'Public Client Standalone Launch with OpenID Connect'
4
+ short_title 'Public Client Launch'
5
5
  input_instructions %(
6
6
  Register Inferno as a standalone application using the following information:
7
7
 
@@ -12,6 +12,27 @@ module ONCCertificationG10TestKit
12
12
  fhirUser), refresh tokens (offline_access), and patient context
13
13
  (launch/patient) are required.
14
14
  )
15
+ description %(
16
+
17
+ This scenario verifies the ability of systems to support public clients
18
+ as described in the SMART App Launch implementation specification. Previous
19
+ scenarios have not required the system under test to demonstrate this
20
+ specific type of SMART App Launch client.
21
+
22
+ Prior to executing this test, register Inferno as a public standalone
23
+ application using the following information:
24
+
25
+ * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
26
+
27
+ Inferno will act as a public client redirect the tester to the the
28
+ authorization endpoint so that they may provide any required credentials
29
+ and authorize the application. Upon successful authorization, Inferno will
30
+ exchange the authorization code provided for an access token.
31
+
32
+ For more information on the #{title}:
33
+
34
+ * [Standalone Launch Sequence](http://hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence)
35
+ )
15
36
  id :g10_public_standalone_launch_stu2
16
37
  run_as_group
17
38
 
@@ -106,8 +127,7 @@ module ONCCertificationG10TestKit
106
127
  :smart_authorization_url,
107
128
  :smart_token_url,
108
129
  :authorization_method,
109
- :public_client_auth_type,
110
- :client_auth_encryption_method
130
+ :public_client_auth_type
111
131
 
112
132
  test from: :g10_patient_context,
113
133
  config: {
@@ -134,5 +154,9 @@ module ONCCertificationG10TestKit
134
154
  assert id_token.present?, 'Token response did not provide an id_token as required.'
135
155
  end
136
156
  end
157
+
158
+ children.each do |child|
159
+ child.inputs.delete(:client_auth_encryption_method)
160
+ end
137
161
  end
138
162
  end
@@ -69,6 +69,8 @@ module ONCCertificationG10TestKit
69
69
  V6_PATIENT_COMPARTMENT_RESOURCE_TYPES =
70
70
  (V5_PATIENT_COMPARTMENT_RESOURCE_TYPES + ['Coverage', 'MedicationDispense', 'Specimen']).freeze
71
71
 
72
+ attr_accessor :received_or_requested
73
+
72
74
  def patient_compartment_resource_types
73
75
  return V5_PATIENT_COMPARTMENT_RESOURCE_TYPES if using_us_core_5?
74
76
 
@@ -94,36 +96,43 @@ module ONCCertificationG10TestKit
94
96
  end
95
97
 
96
98
  def scope_version
97
- config.options[:scope_version]
99
+ case received_or_requested
100
+ when 'received'
101
+ config.options[:received_scope_version] || config.options[:scope_version]
102
+ when 'requested'
103
+ config.options[:requested_scope_version] || config.options[:scope_version]
104
+ else
105
+ config.options[:scope_version]
106
+ end
107
+ end
108
+
109
+ def requested_scope_version
110
+ config.options[:requested_scope_version]
98
111
  end
99
112
 
100
113
  def read_format
101
- @read_format ||=
102
- begin
103
- v1_read_format = 'read'
104
- v2_read_format = 'c?ru?d?s?'
105
-
106
- case scope_version
107
- when :v1
108
- "#{v1_read_format} | *"
109
- when :v2
110
- "#{v2_read_format} | *"
111
- else
112
- [v1_read_format, v2_read_format, '*'].join(' | ')
113
- end
114
- end
114
+ v1_read_format = 'read'
115
+ v2_read_format = 'c?ru?d?s?'
116
+
117
+ case scope_version
118
+ when :v1
119
+ "#{v1_read_format} | *"
120
+ when :v2
121
+ "#{v2_read_format} | *"
122
+ else
123
+ [v1_read_format, v2_read_format, '*'].join(' | ')
124
+ end
115
125
  end
116
126
 
117
127
  def access_level_regex
118
- @access_level_regex ||=
119
- case scope_version
120
- when :v1
121
- /\A(\*|read)\b/
122
- when :v2
123
- /\A(\*|c?ru?d?s?)\b/
124
- else
125
- /\A(\*|read|c?ru?d?s?)\b/
126
- end
128
+ case scope_version
129
+ when :v1
130
+ /\A(\*|read)\b/
131
+ when :v2
132
+ /\A(\*|c?ru?d?s?)\b/
133
+ else
134
+ /\A(\*|read|c?ru?d?s?)\b/
135
+ end
127
136
  end
128
137
 
129
138
  def bad_format_message(scope, scope_direction = 'Requested')
@@ -229,7 +238,7 @@ module ONCCertificationG10TestKit
229
238
  }
230
239
  ].each do |metadata|
231
240
  scopes = metadata[:scopes].split
232
- received_or_requested = metadata[:received_or_requested]
241
+ self.received_or_requested = metadata[:received_or_requested]
233
242
 
234
243
  missing_scopes = required_scopes - scopes
235
244
  assert missing_scopes.empty?,
@@ -24,16 +24,25 @@ module ONCCertificationG10TestKit
24
24
  )
25
25
 
26
26
  description %(
27
- This scenario demonstrates the ability of a system to perform a Patient
27
+ This scenario verifies the ability of a system to perform a single
28
+ SMART App Launch. Specifically, this scenario performs a Patient
28
29
  Standalone Launch to a SMART on FHIR confidential client with a patient
29
30
  context, refresh token, OpenID Connect (OIDC) identity token, and use
30
- the GET HTTP method for code exchange. After launch, a simple Patient
31
- resource read is performed on the patient in context. The access token
32
- is then refreshed, and the Patient resource is read using the new access
33
- token to ensure that the refresh was successful. The authentication
34
- information provided by OpenID Connect is decoded and validated, and
35
- simple queries are performed to ensure that access is granted to all
36
- USCDI data elements.
31
+ the GET HTTP method for code exchange.
32
+
33
+ After launch, a simple Patient resource read is performed on the patient
34
+ in context. The access token is then refreshed, and the Patient resource
35
+ is read using the new access token to ensure that the refresh was
36
+ successful. The authentication information provided by OpenID Connect is
37
+ decoded and validated, and simple queries are performed to ensure that
38
+ access is granted to all USCDI data elements.
39
+
40
+ Prior to running the scenario, register Inferno as a confidential client
41
+ with the following information:
42
+
43
+ * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
44
+
45
+ The following implementation specifications are relevant to this scenario:
37
46
 
38
47
  * [SMART on FHIR
39
48
  (STU1)](http://www.hl7.org/fhir/smart-app-launch/1.0.0/)
@@ -93,12 +102,14 @@ module ONCCertificationG10TestKit
93
102
  'launch-standalone',
94
103
  'client-public',
95
104
  'client-confidential-symmetric',
105
+ 'client-confidential-asymmetric',
96
106
  'sso-openid-connect',
97
107
  'context-standalone-patient',
98
108
  'permission-offline',
99
109
  'permission-patient',
100
110
  'authorize-post',
101
- 'permission-v2'
111
+ 'permission-v2',
112
+ 'permission-v1'
102
113
  ]
103
114
  }
104
115
  }
@@ -0,0 +1,241 @@
1
+ require_relative 'base_token_refresh_group'
2
+ require_relative 'patient_context_test'
3
+ require_relative 'smart_invalid_token_refresh_test'
4
+ require_relative 'smart_scopes_test'
5
+ require_relative 'unauthorized_access_test'
6
+ require_relative 'unrestricted_resource_type_access_group'
7
+ require_relative 'well_known_capabilities_test'
8
+ require_relative 'incorrectly_permitted_tls_versions_messages_setup_test'
9
+
10
+ module ONCCertificationG10TestKit
11
+ class SmartV1ScopesGroup < Inferno::TestGroup
12
+ title 'App Launch with SMART v1 scopes'
13
+ short_title 'Launch with v1 Scopes'
14
+
15
+ input_instructions %(
16
+ Register Inferno as a standalone application using the following information:
17
+
18
+ * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
19
+
20
+ Enter in the appropriate v1 scopes to enable patient-level access to all
21
+ relevant resources. In addition, support for the OpenID Connect (openid
22
+ fhirUser), refresh tokens (offline_access), and patient context
23
+ (launch/patient) are required.
24
+ )
25
+
26
+ description %(
27
+ This scenario verifies the ability of a system to support a
28
+ Standalone Launch when v1 scopes are requested by the client.
29
+ It verifies that systems implement the `permission-v1` capability as required.
30
+ Previous scenarios focus on the use of the `permission-v2` capability,
31
+ and thus a dedicated launch is required to verify that systems
32
+ can support a client that requests `permission-v1` style scopes.
33
+
34
+ This scenario does not place any constraints on the form of scopes
35
+ granted. Systems are free to grant v1-style scopes in response to the
36
+ request for v1-style scopes, as recommended in the [SMART App Launch Guide STU2](http://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html#scopes-for-requesting-fhir-resources).
37
+ Or they can upgrade them to v2-style scopes. The scenario only ensures
38
+ that systems can grant access to clients that request v1-style scopes
39
+ and that the client has access to resources as expected.
40
+
41
+ All relevant resource types must be granted, in a similar manner to the
42
+ 'Standalone Patient App' scenario.
43
+
44
+ This scenario expects Inferno to be registered as a 'Confidential
45
+ Symmetric' client. Systems may either reuse a `client_id` associated
46
+ with Inferno used in a previous scenario, or register Inferno with a new
47
+ `client_id` as a standalone client with the following information:
48
+
49
+ * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
50
+
51
+ )
52
+
53
+ id :g10_smart_v1_scopes
54
+ run_as_group
55
+
56
+ config(
57
+ inputs: {
58
+ client_secret: {
59
+ optional: false,
60
+ name: :standalone_client_secret
61
+ },
62
+ requested_scopes: {
63
+ name: :v1_requested_scopes,
64
+ default: %(
65
+ launch/patient openid fhirUser offline_access
66
+ patient/Medication.read patient/AllergyIntolerance.read
67
+ patient/CarePlan.read patient/CareTeam.read patient/Condition.read
68
+ patient/Device.read patient/DiagnosticReport.read
69
+ patient/DocumentReference.read patient/Encounter.read
70
+ patient/Goal.read patient/Immunization.read patient/Location.read
71
+ patient/MedicationRequest.read patient/Observation.read
72
+ patient/Organization.read patient/Patient.read
73
+ patient/Practitioner.read patient/Procedure.read
74
+ patient/Provenance.read patient/PractitionerRole.read
75
+ patient/Specimen.read patient/Coverage.read
76
+ patient/MedicationDispense.read patient/ServiceRequest.read
77
+ ).gsub(/\s{2,}/, ' ').strip
78
+ },
79
+ received_scopes: { name: :v1_received_scopes },
80
+ smart_credentials: { name: :v1_smart_credentials }
81
+ },
82
+ outputs: {
83
+ received_scopes: { name: :v1_received_scopes },
84
+ patient_id: { name: :v1_patient_id }
85
+ }
86
+ )
87
+
88
+ input_order :url,
89
+ :standalone_client_id,
90
+ :standalone_client_secret,
91
+ :v1_requested_scopes,
92
+ :use_pkce,
93
+ :pkce_code_challenge_method,
94
+ :standalone_authorization_method,
95
+ :client_auth_type,
96
+ :client_auth_encryption_method
97
+
98
+ group from: :smart_discovery_stu2 do
99
+ test from: 'g10_smart_well_known_capabilities',
100
+ config: {
101
+ options: {
102
+ required_capabilities: [
103
+ 'launch-standalone',
104
+ 'client-public',
105
+ 'client-confidential-symmetric',
106
+ 'client-confidential-asymmetric',
107
+ 'sso-openid-connect',
108
+ 'context-standalone-patient',
109
+ 'permission-offline',
110
+ 'permission-patient',
111
+ 'authorize-post',
112
+ 'permission-v2',
113
+ 'permission-v1'
114
+ ]
115
+ }
116
+ }
117
+ end
118
+
119
+ group from: :smart_standalone_launch_stu2,
120
+ config: {
121
+ inputs: {
122
+ use_pkce: {
123
+ default: 'true',
124
+ locked: true
125
+ },
126
+ pkce_code_challenge_method: {
127
+ locked: true
128
+ },
129
+ authorization_method: {
130
+ name: :standalone_authorization_method,
131
+ default: 'get',
132
+ locked: true
133
+ },
134
+ client_auth_type: {
135
+ locked: true,
136
+ default: 'confidential_symmetric'
137
+ }
138
+ },
139
+ outputs: {
140
+ smart_credentials: { name: :v1_smart_credentials }
141
+ }
142
+ } do
143
+ title 'Standalone Launch With Patient Scope'
144
+ description %(
145
+ # Background
146
+
147
+ The [Standalone
148
+ Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch)
149
+ allows an app, like Inferno, to be launched independent of an
150
+ existing EHR session. It is one of the two launch methods described in
151
+ the SMART App Launch Framework alongside EHR Launch. The app will
152
+ request authorization for the provided scope from the authorization
153
+ endpoint, ultimately receiving an authorization token which can be used
154
+ to gain access to resources on the FHIR server.
155
+
156
+ # Test Methodology
157
+
158
+ Inferno will redirect the user to the the authorization endpoint so that
159
+ they may provide any required credentials and authorize the application.
160
+ Upon successful authorization, Inferno will exchange the authorization
161
+ code provided for an access token.
162
+
163
+ For more information on the #{title}:
164
+
165
+ * [Standalone Launch
166
+ Sequence](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch)
167
+ )
168
+
169
+ test from: :g10_smart_scopes do
170
+ config(
171
+ options: {
172
+ requested_scope_version: :v1,
173
+ received_scope_version: :any,
174
+ required_scope_type: 'patient',
175
+ required_scopes: ['openid', 'fhirUser', 'launch/patient', 'offline_access']
176
+ }
177
+ )
178
+ end
179
+
180
+ test from: :g10_unauthorized_access,
181
+ config: {
182
+ inputs: {
183
+ patient_id: { name: :v1_patient_id }
184
+ }
185
+ }
186
+
187
+ test from: :g10_patient_context,
188
+ config: {
189
+ inputs: {
190
+ patient_id: { name: :v1_patient_id },
191
+ smart_credentials: { name: :v1_smart_credentials }
192
+ }
193
+ }
194
+
195
+ tests[0].config(
196
+ outputs: {
197
+ incorrectly_permitted_tls_versions_messages: {
198
+ name: :auth_incorrectly_permitted_tls_versions_messages
199
+ }
200
+ }
201
+ )
202
+
203
+ tests[3].config(
204
+ outputs: {
205
+ incorrectly_permitted_tls_versions_messages: {
206
+ name: :token_incorrectly_permitted_tls_versions_messages
207
+ }
208
+ }
209
+ )
210
+ end
211
+
212
+ group from: :g10_unrestricted_resource_type_access,
213
+ config: {
214
+ inputs: {
215
+ received_scopes: { name: :v1_received_scopes },
216
+ patient_id: { name: :v1_patient_id },
217
+ smart_credentials: { name: :v1_smart_credentials }
218
+ }
219
+ }
220
+
221
+ test from: :g10_incorrectly_permitted_tls_versions_messages_setup,
222
+ id: :g10_auth_incorrectly_permitted_tls_versions_messages_setup,
223
+ config: {
224
+ inputs: {
225
+ incorrectly_permitted_tls_versions_messages: {
226
+ name: :auth_incorrectly_permitted_tls_versions_messages
227
+ }
228
+ }
229
+ }
230
+
231
+ test from: :g10_incorrectly_permitted_tls_versions_messages_setup,
232
+ id: :g10_token_incorrectly_permitted_tls_versions_messages_setup,
233
+ config: {
234
+ inputs: {
235
+ incorrectly_permitted_tls_versions_messages: {
236
+ name: :token_incorrectly_permitted_tls_versions_messages
237
+ }
238
+ }
239
+ }
240
+ end
241
+ end
@@ -9,6 +9,8 @@ module ONCCertificationG10TestKit
9
9
  class GenerateMatrix
10
10
  include ONCCertificationG10TestKit
11
11
 
12
+ attr_accessor :row
13
+
12
14
  FILE_NAME = 'onc_certification_g10_matrix.xlsx'.freeze
13
15
 
14
16
  def inferno_to_procedure_map
@@ -35,6 +37,10 @@ module ONCCertificationG10TestKit
35
37
  @workbook ||= RubyXL::Workbook.new
36
38
  end
37
39
 
40
+ def next_row
41
+ self.row += 1
42
+ end
43
+
38
44
  def run
39
45
  generate_matrix_worksheet
40
46
  generate_test_procedure_worksheet
@@ -44,6 +50,10 @@ module ONCCertificationG10TestKit
44
50
  workbook.write(FILE_NAME)
45
51
  end
46
52
 
53
+ def all_descendant_tests(runnable)
54
+ runnable.tests + runnable.groups.flat_map { |group| all_descendant_tests(group) }
55
+ end
56
+
47
57
  def generate_matrix_worksheet # rubocop:disable Metrics/CyclomaticComplexity
48
58
  matrix_worksheet = workbook.worksheets[0]
49
59
  matrix_worksheet.sheet_name = 'Matrix'
@@ -64,11 +74,15 @@ module ONCCertificationG10TestKit
64
74
  next if group.short_id == '6' # Skip US Core 5
65
75
 
66
76
  matrix_worksheet.add_cell(1, col, group.title).change_text_wrap(true)
67
- matrix_worksheet.merge_cells(1, col, 1, col + group.groups.length - 1)
77
+ matrix_worksheet.merge_cells(1, col, 1, col + group.groups.length - 1) if group.groups.length.positive?
68
78
  matrix_worksheet.change_column_border(col, :left, 'medium')
69
79
  matrix_worksheet.change_column_border_color(col, :left, '000000')
70
80
  column_borders << col
71
81
 
82
+ group.tests.each do |test|
83
+ column_map[test.short_id] = col
84
+ end
85
+
72
86
  group.groups.each do |test_case|
73
87
  matrix_worksheet.change_column_width(col, 4.2)
74
88
 
@@ -80,11 +94,7 @@ module ONCCertificationG10TestKit
80
94
  matrix_worksheet.change_column_border(col, :right, 'thin')
81
95
  matrix_worksheet.change_column_border_color(col, :right, '666666')
82
96
 
83
- test_case.tests.each do |test|
84
- # tests << { test_case: test_case, test: test }
85
- # full_test_id = "#{test_case.prefix}#{test.id}"
86
- column_map[test.short_id] = col
87
- end
97
+ all_descendant_tests(test_case).each { |test| column_map[test.short_id] = col }
88
98
  col += 1
89
99
  end
90
100
  end
@@ -94,7 +104,7 @@ module ONCCertificationG10TestKit
94
104
  matrix_worksheet.change_row_horizontal_alignment(0, 'center')
95
105
 
96
106
  matrix_worksheet.add_cell(2, total_width + 2, 'Supported?')
97
- row = 3
107
+ self.row = 3
98
108
 
99
109
  test_procedure.sections.each do |section|
100
110
  section.steps.each do |step|
@@ -120,7 +130,7 @@ module ONCCertificationG10TestKit
120
130
 
121
131
  matrix_worksheet.add_cell(row, total_width + 2, step.inferno_supported.upcase)
122
132
 
123
- row += 1
133
+ next_row
124
134
  end
125
135
  end
126
136
  matrix_worksheet.change_column_horizontal_alignment(1, 'right')
@@ -161,14 +171,14 @@ module ONCCertificationG10TestKit
161
171
  'Inferno Notes',
162
172
  'Alternate Test Methodology'].each_with_index { |text, index| tp_worksheet.add_cell(0, index, text) }
163
173
 
164
- row = 2
174
+ self.row = 2
165
175
 
166
176
  test_procedure.sections.each do |section|
167
177
  tp_worksheet.add_cell(row, 0, section.name)
168
- row += 1
178
+ next_row
169
179
  section.steps.group_by(&:group).each do |group_name, steps|
170
180
  tp_worksheet.add_cell(row, 1, group_name)
171
- row += 1
181
+ next_row
172
182
  steps.each do |step|
173
183
  longest_line = [step.s_u_t, step.t_l_v, step.inferno_notes, step.alternate_test].map do |text|
174
184
  text&.lines&.count || 0
@@ -183,10 +193,10 @@ module ONCCertificationG10TestKit
183
193
  tp_worksheet.add_cell(row, 7, step.inferno_tests.join(', ')).change_text_wrap(true)
184
194
  tp_worksheet.add_cell(row, 8, step.inferno_notes).change_text_wrap(true)
185
195
  tp_worksheet.add_cell(row, 9, step.alternate_test).change_text_wrap(true)
186
- row += 1
196
+ next_row
187
197
  end
188
198
  end
189
- row += 1
199
+ next_row
190
200
  end
191
201
  end
192
202
 
@@ -198,61 +208,75 @@ module ONCCertificationG10TestKit
198
208
  runnable_and_parents.map(&:suite_option_requirements).compact.flatten
199
209
  end
200
210
 
201
- def generate_inferno_test_worksheet # rubocop:disable Metrics/CyclomaticComplexity
202
- workbook.add_worksheet('Inferno Tests')
203
- inferno_worksheet = workbook.worksheets[2]
211
+ def inferno_worksheet
212
+ workbook.worksheets[2]
213
+ end
204
214
 
205
- columns = [
215
+ def columns # rubocop:disable Metrics/CyclomaticComplexity
216
+ @columns ||= [
206
217
  ['', 3, ->(_test) { '' }],
207
218
  ['', 3, ->(_test) { '' }],
208
219
  ['Inferno Test ID', 22, ->(test) { test.short_id.to_s }],
209
220
  ['Inferno Test Name', 65, ->(test) { test.title }],
210
221
  ['Inferno Test Description', 65, lambda do |test|
211
- description = test.description || ''
212
- natural_indent =
213
- description
214
- .lines
215
- .collect { |l| l.index(/[^ ]/) }
216
- .select { |l| !l.nil? && l.positive? }
217
- .min || 0
218
- description.lines.map { |l| l[natural_indent..] || "\n" }.join.strip
219
- end],
222
+ description = test.description || ''
223
+ natural_indent =
224
+ description
225
+ .lines
226
+ .collect { |l| l.index(/[^ ]/) }
227
+ .select { |l| !l.nil? && l.positive? }
228
+ .min || 0
229
+ description.lines.map { |l| l[natural_indent..] || "\n" }.join.strip
230
+ end],
220
231
  ['Test Procedure Steps', 30, ->(test) { inferno_to_procedure_map[test.short_id].join(', ') }],
221
232
  ['Standard Version Filter', 30, lambda do |test|
222
- applicable_options(test).map(&:value).uniq.join(', ')
223
- end]
224
-
233
+ applicable_options(test).map(&:value).uniq.join(', ')
234
+ end]
225
235
  ]
236
+ end
237
+
238
+ def add_test(test)
239
+ this_row = columns.map do |column|
240
+ column[2].call(test)
241
+ end
242
+
243
+ this_row.each_with_index do |value, index|
244
+ inferno_worksheet.add_cell(row, index, value).change_text_wrap(true)
245
+ end
246
+ inferno_worksheet
247
+ .change_row_height(row, [26, ((test.description || '').strip.lines.count * 10) + 10].max)
248
+ inferno_worksheet.change_row_vertical_alignment(row, 'top')
249
+ next_row
250
+ end
251
+
252
+ def add_group_title(group, column: 1)
253
+ inferno_worksheet.add_cell(row, column, "#{group.short_id}: #{group.title}")
254
+ inferno_worksheet.add_cell(row, 6, applicable_options(group).map(&:value).uniq.join(', '))
255
+ next_row
256
+ end
257
+
258
+ def add_group(group)
259
+ add_group_title(group)
260
+
261
+ group.tests.each { |test| add_test(test) }
262
+
263
+ group.groups.each { |nested_group| add_group(nested_group) }
264
+ end
265
+
266
+ def generate_inferno_test_worksheet
267
+ workbook.add_worksheet('Inferno Tests')
226
268
 
227
269
  columns.each_with_index do |row_name, index|
228
270
  inferno_worksheet.add_cell(0, index, row_name.first)
229
271
  end
230
272
 
231
- row = 1
273
+ self.row = 1
232
274
 
233
275
  test_suite.groups.each do |group|
234
- row += 1
235
- inferno_worksheet.add_cell(row, 0, "#{group.short_id}: #{group.title}")
236
- inferno_worksheet.add_cell(row, 6, applicable_options(group).map(&:value).uniq.join(', '))
237
- row += 1
238
- group.groups.each do |test_case|
239
- inferno_worksheet.add_cell(row, 1, "#{test_case.short_id}: #{test_case.title}")
240
- inferno_worksheet.add_cell(row, 6, applicable_options(test_case).map(&:value).uniq.join(', '))
241
-
242
- row += 1
243
- test_case.tests.each do |test|
244
- this_row = columns.map do |column|
245
- column[2].call(test)
246
- end
276
+ next if group.short_id == '6' # Skip US Core 5
247
277
 
248
- this_row.each_with_index do |value, index|
249
- inferno_worksheet.add_cell(row, index, value).change_text_wrap(true)
250
- end
251
- inferno_worksheet.change_row_height(row, [26, ((test.description || '').strip.lines.count * 10) + 10].max)
252
- inferno_worksheet.change_row_vertical_alignment(row, 'top')
253
- row += 1
254
- end
255
- end
278
+ next_row
279
+ add_group(group)
256
280
  end
257
281
 
258
282
  columns.each_with_index do |column, index|