inferno_core 0.6.10 → 0.6.12

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/cli/evaluate.rb +8 -7
  3. data/lib/inferno/apps/cli/execute.rb +17 -21
  4. data/lib/inferno/apps/cli/main.rb +12 -1
  5. data/lib/inferno/apps/cli/requirements.rb +49 -0
  6. data/lib/inferno/apps/cli/requirements_coverage_checker.rb +238 -0
  7. data/lib/inferno/apps/cli/suite.rb +9 -0
  8. data/lib/inferno/apps/web/controllers/test_sessions/results/io_value.rb +68 -0
  9. data/lib/inferno/apps/web/router.rb +13 -5
  10. data/lib/inferno/apps/web/serializers/result.rb +2 -2
  11. data/lib/inferno/apps/web/serializers/serializer.rb +7 -0
  12. data/lib/inferno/apps/web/serializers/test.rb +1 -1
  13. data/lib/inferno/apps/web/serializers/test_group.rb +1 -1
  14. data/lib/inferno/apps/web/serializers/test_suite.rb +2 -2
  15. data/lib/inferno/dsl/fhir_client.rb +9 -1
  16. data/lib/inferno/dsl/http_client.rb +9 -1
  17. data/lib/inferno/dsl/runnable.rb +15 -30
  18. data/lib/inferno/dsl/suite_requirements.rb +26 -26
  19. data/lib/inferno/entities/result.rb +39 -0
  20. data/lib/inferno/entities/test_kit.rb +4 -0
  21. data/lib/inferno/feature.rb +9 -0
  22. data/lib/inferno/public/bundle.js +34 -34
  23. data/lib/inferno/repositories/repository.rb +19 -0
  24. data/lib/inferno/repositories/requirements.rb +4 -3
  25. data/lib/inferno/repositories/results.rb +6 -3
  26. data/lib/inferno/repositories/runnable_repository.rb +29 -0
  27. data/lib/inferno/repositories/test_groups.rb +2 -2
  28. data/lib/inferno/repositories/test_sessions.rb +1 -0
  29. data/lib/inferno/repositories/test_suites.rb +2 -2
  30. data/lib/inferno/repositories/tests.rb +2 -2
  31. data/lib/inferno/repositories.rb +1 -0
  32. data/lib/inferno/version.rb +1 -1
  33. metadata +6 -3
  34. data/spec/features_helper.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ff5c612e8052b2c2a817e5ad58d27d935b8838adc2d738a8924e61ba7ecc1a0
4
- data.tar.gz: 4324693af54d9c83d4eb60f2a2b85dc8012c72d372d9858bda39693d2b1de8b4
3
+ metadata.gz: a7e5f22313a778dd7b70ba70eae9513bda158a7c0cd6beba9a2d195b3a37f077
4
+ data.tar.gz: 8e7d6155fad17e00ccc48257853f4df2cc816d0965cc0b18a50515bbd26d343f
5
5
  SHA512:
6
- metadata.gz: 2cbff301066bd9b295edc08790bbf8b6d658d2cd5c647a636a09c5918ff0024fc06eb566742d271bf6ea1a145745072f16ab157a674b6be3e8076a298d8110fd
7
- data.tar.gz: 5a9281d55a608b9356d535c5e7fc0b9bc8afb6d23a5cc6f76192e074582d385358b81ff07dd8959286440634b08411d8866b306313c4826d81ca85eb02a4d225
6
+ metadata.gz: 56b26537ac370a6b475ab33895ad91e3d5cf5ff588cd3fa5be979c6e250e5aa703a76c093ea46c787ecd2c4f061f1f55e39c43ba245219618c18b9a44e522f3e
7
+ data.tar.gz: a1549f23c6e8c501286bc490c6821be19da1a730b950a497bdd7a2c7d61c392090e0ac1603f251e4d033abedbc4457e1dfcbf7ecac5bcfcd875392e7125ca4ef
@@ -27,10 +27,11 @@ module Inferno
27
27
 
28
28
  ig_path = absolute_path_with_home_expansion(ig_path)
29
29
  data_path = absolute_path_with_home_expansion(data_path) if data_path
30
+ config_path = absolute_path_with_home_expansion(options[:config]) if options[:config]
30
31
 
31
32
  Dir.chdir(tmpdir) do
32
33
  Migration.new.run(Logger::FATAL) # Hide migration output for evaluator
33
- evaluate(ig_path, data_path, options)
34
+ evaluate(ig_path, data_path, config_path, options)
34
35
  end
35
36
  ensure
36
37
  system("#{services_base_command} down #{services_names}")
@@ -57,14 +58,14 @@ module Inferno
57
58
  end
58
59
 
59
60
  # @see Inferno::CLI::Main#evaluate
60
- def evaluate(ig_path, data_path, options)
61
+ def evaluate(ig_path, data_path, config_path, options)
61
62
  # NOTE: repositories is required here rather than at the top of the file because
62
63
  # the tree of requires means that this file and its requires get required by every CLI app.
63
64
  # Sequel::Model, used in some repositories, fetches the table schema at instantiation.
64
65
  # This breaks the `migrate` task by fetching a table before the task runs/creates it.
65
66
  require_relative '../../repositories'
66
67
 
67
- validate_args(ig_path, data_path)
68
+ validate_args(ig_path, data_path, config_path)
68
69
  ig = Inferno::Repositories::IGs.new.find_or_load(ig_path)
69
70
 
70
71
  check_ig_version(ig)
@@ -80,17 +81,17 @@ module Inferno
80
81
 
81
82
  evaluator = Inferno::DSL::FHIREvaluation::Evaluator.new(ig, validator)
82
83
 
83
- config = Inferno::DSL::FHIREvaluation::Config.new
84
+ config = Inferno::DSL::FHIREvaluation::Config.new(config_path)
84
85
  results = evaluator.evaluate(data, config)
85
86
  output_results(results, options[:output])
86
87
  end
87
88
 
88
- def validate_args(ig_path, data_path)
89
+ def validate_args(ig_path, data_path, config_path)
89
90
  raise 'A path to an IG is required!' unless ig_path
90
91
 
91
- return unless data_path && (!File.directory? data_path)
92
+ raise "Provided path '#{data_path}' is not a directory" if data_path && (!File.directory? data_path)
92
93
 
93
- raise "Provided path '#{data_path}' is not a directory"
94
+ raise "Provided path '#{config_path}' does not exist" if config_path && (!File.exist? config_path)
94
95
  end
95
96
 
96
97
  def check_ig_version(ig)
@@ -6,6 +6,7 @@ Dir[File.join(__dir__, 'execute', '*_outputter.rb')].each { |outputter| require
6
6
 
7
7
  module Inferno
8
8
  module CLI
9
+ # @private
9
10
  class Execute
10
11
  include ::Inferno::Utils::VerifyRunnable
11
12
  include ::Inferno::Utils::PersistInputs
@@ -45,7 +46,6 @@ module Inferno
45
46
  self.options = options
46
47
 
47
48
  outputter.print_start_message(self.options)
48
-
49
49
  load_preset_file_and_set_preset_id
50
50
 
51
51
  results = []
@@ -104,18 +104,6 @@ module Inferno
104
104
  @outputter ||= OUTPUTTERS[options[:outputter]].new
105
105
  end
106
106
 
107
- def load_preset_file_and_set_preset_id
108
- return unless options[:preset_file]
109
- raise StandardError, 'Cannot use `--preset-id` and `--preset-file` options together' if options[:preset_id]
110
-
111
- raise StandardError, "File #{options[:preset_file]} not found" unless File.exist? options[:preset_file]
112
-
113
- options[:preset_id] = JSON.parse(File.read(options[:preset_file]))['id']
114
- raise StandardError, "Preset #{options[:preset_file]} is missing id" if options[:preset_id].nil?
115
-
116
- presets_repo.insert_from_file(options[:preset_file])
117
- end
118
-
119
107
  def all_selected_groups_and_tests
120
108
  @all_selected_groups_and_tests ||= runnables_by_short_id + groups + tests
121
109
  end
@@ -123,7 +111,7 @@ module Inferno
123
111
  def run_one(runnable, test_run)
124
112
  verify_runnable(
125
113
  runnable,
126
- thor_hash_to_inputs_array(inputs_and_preset),
114
+ all_inputs,
127
115
  test_session.suite_options
128
116
  )
129
117
 
@@ -132,15 +120,14 @@ module Inferno
132
120
  dispatch_job(test_run)
133
121
  end
134
122
 
135
- def inputs_and_preset
123
+ def all_inputs
136
124
  if preset
137
- preset_inputs = preset.inputs.to_h do |preset_input|
138
- [preset_input[:name], preset_input[:value]]
139
- end
125
+ test_sessions_repo.apply_preset(test_session, preset.id)
126
+ preset_inputs = session_data_repo.get_all_from_session(test_session.id)
140
127
 
141
- options.fetch(:inputs, {}).reverse_merge(preset_inputs)
128
+ thor_hash_to_inputs_array(options.fetch(:inputs, {})) + preset_inputs.map(&:to_hash)
142
129
  else
143
- options.fetch(:inputs, {})
130
+ thor_hash_to_inputs_array(options.fetch(:inputs, {}))
144
131
  end
145
132
  end
146
133
 
@@ -159,6 +146,15 @@ module Inferno
159
146
  @preset
160
147
  end
161
148
 
149
+ def load_preset_file_and_set_preset_id
150
+ return unless options[:preset_file]
151
+ raise StandardError, 'Cannot use `--preset-id` and `--preset-file` options together' if options[:preset_id]
152
+
153
+ raise StandardError, "File #{options[:preset_file]} not found" unless File.exist? options[:preset_file]
154
+
155
+ options[:preset_id] = presets_repo.insert_from_file(options[:preset_file]).id
156
+ end
157
+
162
158
  def suite
163
159
  @suite ||= Inferno::Repositories::TestSuites.new.find(options[:suite])
164
160
 
@@ -214,7 +210,7 @@ module Inferno
214
210
  {
215
211
  test_session_id: test_session.id,
216
212
  runnable_id_key(runnable) => runnable.id,
217
- inputs: thor_hash_to_inputs_array(inputs_and_preset)
213
+ inputs: all_inputs
218
214
  }
219
215
  end
220
216
 
@@ -6,8 +6,8 @@ require_relative 'services'
6
6
  require_relative 'suite'
7
7
  require_relative 'suites'
8
8
  require_relative 'new'
9
- require_relative '../../version'
10
9
  require_relative 'execute'
10
+ require_relative '../../version'
11
11
 
12
12
  module Inferno
13
13
  module CLI
@@ -34,6 +34,9 @@ module Inferno
34
34
 
35
35
  # Loads the us core ig and evaluate the data included in the IG's example folder, with results redirected to outcome.json as an OperationOutcome
36
36
  `bundle exec inferno evaluate ./uscore.tgz --output outcome.json`
37
+
38
+ # Loads the us core ig and evaluate the data included in the IG's example folder using a custom configuration file
39
+ `bundle exec inferno evaluate ./uscore.tgz --config ./custom_config.yml`
37
40
  LONGDESC
38
41
  # TODO: Add options below as arguments
39
42
  option :data_path,
@@ -45,6 +48,10 @@ module Inferno
45
48
  aliases: ['-o'],
46
49
  type: :string,
47
50
  desc: 'Export evaluation result to outcome.json as an OperationOutcome'
51
+ option :config,
52
+ aliases: ['-c'],
53
+ type: :string,
54
+ desc: 'Path to a custom configuration file'
48
55
  def evaluate(ig_path)
49
56
  Evaluate.new.run(ig_path, options[:data_path], options)
50
57
  end
@@ -92,6 +99,10 @@ module Inferno
92
99
 
93
100
  desc 'suites', 'List available test suites'
94
101
  def suites
102
+ ENV['NO_DB'] = 'true'
103
+
104
+ require_relative '../../../inferno'
105
+
95
106
  Suites.new.run
96
107
  end
97
108
 
@@ -1,3 +1,4 @@
1
+ require_relative 'requirements_coverage_checker'
1
2
  require_relative 'requirements_exporter'
2
3
 
3
4
  module Inferno
@@ -23,6 +24,54 @@ module Inferno
23
24
  ENV['NO_DB'] = 'true'
24
25
  RequirementsExporter.new.run_check
25
26
  end
27
+
28
+ desc 'coverage [TEST_SUITE_ID]',
29
+ "Check whether all of a test suite's requirements are tested. If no test suite id is provided, " \
30
+ 'all test suites in the current test kit will be checked.'
31
+ long_desc <<~LONGDESC
32
+ Check whether all of the requirements declared by a test suite are
33
+ tested by the tests in the test suite
34
+ LONGDESC
35
+ def coverage(test_suite_id = nil)
36
+ ENV['NO_DB'] = 'true'
37
+
38
+ require_relative '../../../inferno'
39
+
40
+ Inferno::Application.start(:requirements)
41
+
42
+ if test_suite_id.present?
43
+ RequirementsCoverageChecker.new(test_suite_id).run
44
+ else
45
+ Inferno::Repositories::TestSuites.all.each do |test_suite|
46
+ if Object.const_source_location(test_suite.to_s).first.start_with? Dir.pwd
47
+ RequirementsCoverageChecker.new(test_suite.id).run
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ desc 'check_coverage [TEST_SUITE_ID]',
54
+ 'Check whether the coverage CSV files are up to date'
55
+ long_desc <<~LONGDESC
56
+ Check whether the coverage CSV files are up to date
57
+ LONGDESC
58
+ def check_coverage(test_suite_id = nil)
59
+ ENV['NO_DB'] = 'true'
60
+
61
+ require_relative '../../../inferno'
62
+
63
+ Inferno::Application.start(:requirements)
64
+
65
+ if test_suite_id.present?
66
+ RequirementsCoverageChecker.new(test_suite_id).run_check
67
+ else
68
+ Inferno::Repositories::TestSuites.all.each do |test_suite|
69
+ if Object.const_source_location(test_suite.to_s).first.start_with? Dir.pwd
70
+ RequirementsCoverageChecker.new(test_suite.id).run_check
71
+ end
72
+ end
73
+ end
74
+ end
26
75
  end
27
76
  end
28
77
  end
@@ -0,0 +1,238 @@
1
+ require_relative 'requirements_exporter'
2
+
3
+ module Inferno
4
+ module CLI
5
+ class RequirementsCoverageChecker
6
+ attr_accessor :test_suite_id, :test_suite
7
+
8
+ def initialize(test_suite_id)
9
+ self.test_suite_id = test_suite_id
10
+ self.test_suite = Inferno::Repositories::TestSuites.new.find(test_suite_id)
11
+ end
12
+
13
+ def short_id_header
14
+ "#{test_suite.title} Short ID(s)"
15
+ end
16
+
17
+ def full_id_header
18
+ "#{test_suite.title} Full ID(s)"
19
+ end
20
+
21
+ def output_headers
22
+ [
23
+ *(RequirementsExporter::REQUIREMENTS_OUTPUT_HEADERS - ['Sub-Requirement(s)']),
24
+ short_id_header,
25
+ full_id_header
26
+ ]
27
+ end
28
+
29
+ def test_kit_name
30
+ local_test_kit_gem.name
31
+ end
32
+
33
+ def base_requirements_folder
34
+ RequirementsExporter.new.base_requirements_folder
35
+ end
36
+
37
+ def output_folder
38
+ @output_folder ||= File.join(base_requirements_folder, 'generated')
39
+ end
40
+
41
+ def output_file_name
42
+ "#{test_suite_id}_requirements_coverage.csv"
43
+ end
44
+
45
+ def output_file_path
46
+ @output_file_path ||= File.join(output_folder, output_file_name)
47
+ end
48
+
49
+ def suite_requirements
50
+ @suite_requirements ||=
51
+ Inferno::Repositories::Requirements.new.requirements_for_suite(test_suite_id)
52
+ end
53
+
54
+ def tested_requirement_ids
55
+ @tested_requirement_ids ||= test_suite.all_verified_requirements
56
+ end
57
+
58
+ def suite_runnables
59
+ @suite_runnables ||= [test_suite] + test_suite.all_descendants
60
+ end
61
+
62
+ def untested_requirements
63
+ @untested_requirements ||= []
64
+ end
65
+
66
+ def new_csv # rubocop:disable Metrics/CyclomaticComplexity
67
+ @new_csv ||=
68
+ CSV.generate(+"\xEF\xBB\xBF") do |csv|
69
+ csv << output_headers
70
+
71
+ suite_requirements.each do |requirement|
72
+ if requirement.not_tested_reason.present?
73
+ long_ids = 'NA'
74
+ short_ids = 'NA'
75
+ else
76
+ runnables_for_requirement =
77
+ suite_runnables.select { |runnable| runnable.verifies_requirements.include? requirement.id }
78
+ long_ids = runnables_for_requirement&.map(&:id)&.join(', ')
79
+ short_ids =
80
+ runnables_for_requirement
81
+ &.map { |runnable| runnable < Inferno::Entities::TestSuite ? 'suite' : runnable.short_id }
82
+ &.join(', ')
83
+ end
84
+
85
+ untested_requirements << runnables_for_requirement if runnables_for_requirement.blank?
86
+
87
+ row = [
88
+ requirement.requirement_set,
89
+ requirement.id.delete_prefix("#{requirement.requirement_set}@"),
90
+ requirement.url,
91
+ requirement.requirement,
92
+ requirement.conformance,
93
+ requirement.actor,
94
+ requirement.conditionality,
95
+ requirement.not_tested_reason,
96
+ requirement.not_tested_details,
97
+ short_ids,
98
+ long_ids
99
+ ]
100
+
101
+ csv << row
102
+ end
103
+ end
104
+ end
105
+
106
+ def input_requirement_ids
107
+ @input_requirement_ids ||= input_rows.map { |row| "#{row['Req Set']}@#{row['ID']}" }
108
+ end
109
+
110
+ # The requirements present in Inferno that aren't in the input spreadsheet
111
+ def unmatched_requirement_ids
112
+ @unmatched_requirement_ids ||=
113
+ tested_requirement_ids - suite_requirements.map(&:id)
114
+ end
115
+
116
+ def unmatched_requirement_rows
117
+ unmatched_requirement_ids.flat_map do |requirement_id|
118
+ runnables_for_requirement =
119
+ suite_runnables.select { |runnable| runnable.verifies_requirements.include? requirement_id }
120
+
121
+ runnables_for_requirement.map do |runnable|
122
+ [requirement_id, runnable.short_id, runnable.id]
123
+ end
124
+ end
125
+ end
126
+
127
+ def old_csv
128
+ @old_csv ||= File.read(output_file_path)
129
+ end
130
+
131
+ def run
132
+ unless test_suite.present?
133
+ puts "Could not find test suite: #{test_suite_id}. Aborting requirements coverage generation..."
134
+ exit(1)
135
+ end
136
+
137
+ puts
138
+
139
+ if unmatched_requirement_ids.present?
140
+ puts "WARNING: The following requirements indicated in the test suite #{test_suite_id} are not present in " \
141
+ "the suite's requirement sets:"
142
+ output_requirements_map_table(unmatched_requirement_rows)
143
+ end
144
+
145
+ if File.exist?(output_file_path)
146
+ if old_csv == new_csv
147
+ puts "'#{output_file_name}' file is up to date."
148
+ return
149
+ else
150
+ puts 'Requirements coverage has changed.'
151
+ end
152
+ else
153
+ puts "No existing #{output_file_name}."
154
+ end
155
+
156
+ puts "Writing to file #{output_file_path}..."
157
+ FileUtils.mkdir_p(output_folder)
158
+ File.write(output_file_path, new_csv)
159
+ puts 'Done.'
160
+ end
161
+
162
+ def run_check
163
+ unless test_suite.present?
164
+ puts "Could not find test suite: #{test_suite_id}. Aborting requirements coverage generation..."
165
+ exit(1)
166
+ end
167
+
168
+ puts
169
+
170
+ if unmatched_requirement_ids.any?
171
+ puts "WARNING: The following requirements indicated in the test suite #{test_suite_id} are not present in " \
172
+ "the suite's requirement sets:"
173
+ output_requirements_map_table(unmatched_requirement_rows)
174
+ end
175
+
176
+ if File.exist?(output_file_path)
177
+ if old_csv == new_csv
178
+ puts "'#{output_file_name}' file is up to date."
179
+ return unless unmatched_requirement_ids.present?
180
+ else
181
+ puts <<~MESSAGE
182
+ #{output_file_name} file is out of date.
183
+ To regenerate the file, run:
184
+
185
+ bundle exec inferno requirements coverage
186
+
187
+ MESSAGE
188
+ end
189
+ else
190
+ puts <<~MESSAGE
191
+ No existing #{output_file_name} file.
192
+ To generate the file, run:
193
+
194
+ bundle exec inferno requirements coverage
195
+
196
+ MESSAGE
197
+ end
198
+
199
+ puts 'Check failed.'
200
+ exit(1)
201
+ end
202
+
203
+ # Output the requirements in the map like so:
204
+ #
205
+ # requirement_id | short_id | full_id
206
+ # ---------------+------------+----------
207
+ # req-id-1 | short-id-1 | full-id-1
208
+ # req-id-2 | short-id-2 | full-id-2
209
+ #
210
+ def output_requirements_map_table(requirements_rows)
211
+ headers = %w[requirement_id short_id full_id]
212
+ col_widths = headers.map(&:length)
213
+ col_widths[0] = [col_widths[0], *requirements_rows.map { |row| row[0].length }].max
214
+ col_widths[1] = [col_widths[1], *requirements_rows.map { |row| row[1].length }].max
215
+ col_widths[2] = [col_widths[2], *requirements_rows.map { |row| row[2].length }].max
216
+ col_widths.map! { |width| width + 3 }
217
+
218
+ puts [
219
+ headers[0].ljust(col_widths[0]),
220
+ headers[1].ljust(col_widths[1]),
221
+ headers[2].ljust(col_widths[2])
222
+ ].join(' | ')
223
+ puts col_widths.map { |width| '-' * width }.join('-+-')
224
+ output_requirements_map_table_contents(requirements_rows, col_widths)
225
+ end
226
+
227
+ def output_requirements_map_table_contents(requirements_rows, col_widths)
228
+ requirements_rows.each do |requirements_row|
229
+ puts [
230
+ requirements_row[0].ljust(col_widths[0]),
231
+ requirements_row[1].ljust(col_widths[1]),
232
+ requirements_row[2].ljust(col_widths[2])
233
+ ].join(' | ')
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -15,6 +15,9 @@ module Inferno
15
15
  option :filename, banner: '<filename>', aliases: [:f]
16
16
  def input_template(suite_id)
17
17
  ENV['NO_DB'] = 'true'
18
+
19
+ require_relative '../../../inferno'
20
+
18
21
  SuiteInputTemplate.new.run(suite_id, options)
19
22
  end
20
23
 
@@ -24,6 +27,9 @@ module Inferno
24
27
  LONGDESC
25
28
  def describe(suite_id)
26
29
  ENV['NO_DB'] = 'true'
30
+
31
+ require_relative '../../../inferno'
32
+
27
33
  Inferno::Application.start(:suites)
28
34
 
29
35
  suite = Inferno::Repositories::TestSuites.new.find(suite_id)
@@ -59,6 +65,9 @@ module Inferno
59
65
  LONGDESC
60
66
  def lock_short_ids(suite_id)
61
67
  ENV['NO_DB'] = 'true'
68
+
69
+ require_relative '../../../inferno'
70
+
62
71
  Inferno::Application.start(:suites)
63
72
 
64
73
  suite = Inferno::Repositories::TestSuites.new.find(suite_id)
@@ -0,0 +1,68 @@
1
+ module Inferno
2
+ module Web
3
+ module Controllers
4
+ module TestSessions
5
+ module Results
6
+ class IoValue < Controller
7
+ include Import[
8
+ test_sessions_repo: 'inferno.repositories.test_sessions',
9
+ results_repo: 'inferno.repositories.results'
10
+ ]
11
+
12
+ def handle(req, res)
13
+ test_session_id = req.params[:id]
14
+ result_id = req.params[:result_id]
15
+ type = req.params[:type]
16
+ name = req.params[:name]
17
+
18
+ unless %w[inputs outputs].include?(type)
19
+ res.status = 400
20
+ res.body = { error: 'Invalid I/O type. Must be "inputs" or "outputs".' }.to_json
21
+ return
22
+ end
23
+
24
+ test_session_results = results_repo.current_results_for_test_session(test_session_id)
25
+ result = test_session_results.find { |r| r.id == result_id }
26
+
27
+ if result.nil?
28
+ res.status = 404
29
+ res.body = { error: "Result '#{result_id}' not found for test session '#{test_session_id}'." }.to_json
30
+ return
31
+ end
32
+
33
+ entry = result_io_by_name(result, type, name)
34
+ value = entry&.dig('value')
35
+
36
+ if value.blank?
37
+ res.status = 404
38
+ res.body = { error: "#{type.singularize.capitalize} '#{name}' not found or missing value." }.to_json
39
+ return
40
+ end
41
+
42
+ res.body = value.is_a?(Hash) ? value.to_json : value
43
+ res.content_type = content_type_for(value)
44
+ end
45
+
46
+ private
47
+
48
+ def result_io_by_name(result, io_type, name)
49
+ result.public_send(io_type)
50
+ .find { |item| item['name'] == name }
51
+ end
52
+
53
+ def content_type_for(value)
54
+ return 'application/json' if value.is_a?(Hash)
55
+
56
+ trimmed = value&.strip
57
+
58
+ return 'application/json' if trimmed&.start_with?('{', '[')
59
+ return 'application/xml' if trimmed&.start_with?('<') && trimmed.end_with?('>')
60
+
61
+ 'text/plain'
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,4 +1,5 @@
1
1
  require 'erb'
2
+ require_relative '../../feature'
2
3
 
3
4
  Dir.glob(File.join(__dir__, 'controllers', '**', '*.rb')).each { |path| require_relative path }
4
5
 
@@ -33,6 +34,9 @@ module Inferno
33
34
  get '/:id/results',
34
35
  to: Inferno::Web::Controllers::TestSessions::Results::Index,
35
36
  as: :results
37
+ get '/:id/results/:result_id/io/:type/:name',
38
+ to: Inferno::Web::Controllers::TestSessions::Results::IoValue,
39
+ as: :result_io_value
36
40
  get '/:id/session_data',
37
41
  to: Inferno::Web::Controllers::TestSessions::SessionData::Index
38
42
  put '/:id/session_data/apply_preset',
@@ -47,13 +51,17 @@ module Inferno
47
51
  put '/:id/check_configuration',
48
52
  to: Inferno::Web::Controllers::TestSuites::CheckConfiguration,
49
53
  as: :check_configuration
50
- get ':id/requirements',
51
- to: Inferno::Web::Controllers::TestSuites::Requirements::Index,
52
- as: :requirements
54
+ if Feature.requirements_enabled?
55
+ get ':id/requirements',
56
+ to: Inferno::Web::Controllers::TestSuites::Requirements::Index,
57
+ as: :requirements
58
+ end
53
59
  end
54
60
 
55
- scope 'requirements' do
56
- get '/:id', to: Inferno::Web::Controllers::Requirements::Show, as: :show
61
+ if Feature.requirements_enabled?
62
+ scope 'requirements' do
63
+ get '/:id', to: Inferno::Web::Controllers::Requirements::Show, as: :show
64
+ end
57
65
  end
58
66
 
59
67
  get '/requests/:id', to: Inferno::Web::Controllers::Requests::Show, as: :requests_show
@@ -21,11 +21,11 @@ module Inferno
21
21
  field :optional?, name: :optional
22
22
 
23
23
  field :inputs do |result, _options|
24
- result.input_json.present? ? JSON.parse(result.input_json) : []
24
+ result.handle_large_io('inputs')
25
25
  end
26
26
 
27
27
  field :outputs do |result, _options|
28
- result.output_json.present? ? JSON.parse(result.output_json) : []
28
+ result.handle_large_io('outputs')
29
29
  end
30
30
 
31
31
  association :messages, blueprint: Message, if: :field_present?
@@ -1,5 +1,6 @@
1
1
  require 'oj'
2
2
  require 'blueprinter'
3
+ require_relative '../../../feature'
3
4
 
4
5
  module Inferno
5
6
  module Web
@@ -13,6 +14,12 @@ module Inferno
13
14
  result.send(name).present?
14
15
  end
15
16
  end
17
+
18
+ # When removing the feature flag, replace all instances of this method
19
+ # with `.field_present?`
20
+ def self.field_present_and_requirements_enabled?(field_name, result, options)
21
+ field_present?(field_name, result, options) && Feature.requirements_enabled?
22
+ end
16
23
  end
17
24
  end
18
25
  end
@@ -20,7 +20,7 @@ module Inferno
20
20
  field :input_instructions
21
21
  field :user_runnable?, name: :user_runnable
22
22
  field :optional?, name: :optional
23
- field :verifies_requirements, if: :field_present?
23
+ field :verifies_requirements, if: :field_present_and_requirements_enabled?
24
24
  end
25
25
  end
26
26
  end
@@ -32,7 +32,7 @@ module Inferno
32
32
  Input.render_as_hash(group.available_inputs(suite_options).values)
33
33
  end
34
34
  field :output_definitions, name: :outputs, extractor: HashValueExtractor
35
- field :verifies_requirements, if: :field_present?
35
+ field :verifies_requirements, if: :field_present_and_requirements_enabled?
36
36
  end
37
37
  end
38
38
  end