inferno_core 0.0.2 → 0.0.6

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/web/controllers/test_runs/create.rb +30 -10
  3. data/lib/inferno/apps/web/controllers/test_runs/show.rb +10 -0
  4. data/lib/inferno/apps/web/controllers/test_sessions/create.rb +1 -0
  5. data/lib/inferno/apps/web/controllers/test_sessions/last_test_run.rb +22 -0
  6. data/lib/inferno/apps/web/controllers/test_sessions/results/index.rb +6 -1
  7. data/lib/inferno/apps/web/controllers/test_sessions/session_data/index.rb +21 -0
  8. data/lib/inferno/apps/web/router.rb +15 -0
  9. data/lib/inferno/apps/web/serializers/hash_value_extractor.rb +11 -0
  10. data/lib/inferno/apps/web/serializers/request.rb +1 -0
  11. data/lib/inferno/apps/web/serializers/result.rb +8 -0
  12. data/lib/inferno/apps/web/serializers/session_data.rb +10 -0
  13. data/lib/inferno/apps/web/serializers/test.rb +3 -6
  14. data/lib/inferno/apps/web/serializers/test_group.rb +5 -8
  15. data/lib/inferno/apps/web/serializers/test_run.rb +1 -0
  16. data/lib/inferno/apps/web/serializers/test_session.rb +1 -1
  17. data/lib/inferno/apps/web/serializers/test_suite.rb +1 -0
  18. data/lib/inferno/config/application.rb +5 -2
  19. data/lib/inferno/config/boot/db.rb +10 -1
  20. data/lib/inferno/config/boot/sidekiq.rb +11 -0
  21. data/lib/inferno/config/boot/suites.rb +4 -6
  22. data/lib/inferno/config/boot.rb +2 -0
  23. data/lib/inferno/db/migrations/001_create_initial_structure.rb +0 -21
  24. data/lib/inferno/db/migrations/002_add_wait_support.rb +7 -0
  25. data/lib/inferno/db/migrations/003_update_session_data.rb +18 -0
  26. data/lib/inferno/db/migrations/004_add_request_results_table.rb +9 -0
  27. data/lib/inferno/db/migrations/005_add_updated_at_index_to_results.rb +5 -0
  28. data/lib/inferno/db/schema.rb +154 -0
  29. data/lib/inferno/dsl/assertions.rb +20 -0
  30. data/lib/inferno/dsl/configurable.rb +126 -0
  31. data/lib/inferno/dsl/fhir_client.rb +4 -2
  32. data/lib/inferno/dsl/fhir_client_builder.rb +16 -0
  33. data/lib/inferno/dsl/http_client.rb +10 -8
  34. data/lib/inferno/dsl/request_storage.rb +30 -9
  35. data/lib/inferno/dsl/results.rb +49 -0
  36. data/lib/inferno/dsl/resume_test_route.rb +89 -0
  37. data/lib/inferno/dsl/runnable.rb +153 -16
  38. data/lib/inferno/dsl.rb +1 -3
  39. data/lib/inferno/entities/header.rb +7 -7
  40. data/lib/inferno/entities/message.rb +8 -6
  41. data/lib/inferno/entities/request.rb +42 -16
  42. data/lib/inferno/entities/result.rb +34 -18
  43. data/lib/inferno/entities/session_data.rb +33 -0
  44. data/lib/inferno/entities/test.rb +35 -8
  45. data/lib/inferno/entities/test_group.rb +8 -0
  46. data/lib/inferno/entities/test_run.rb +13 -6
  47. data/lib/inferno/entities/test_session.rb +8 -8
  48. data/lib/inferno/entities.rb +1 -1
  49. data/lib/inferno/exceptions.rb +24 -0
  50. data/lib/inferno/jobs/execute_test_run.rb +14 -0
  51. data/lib/inferno/jobs/resume_test_run.rb +14 -0
  52. data/lib/inferno/jobs.rb +16 -0
  53. data/lib/inferno/public/bundle.js +1 -1
  54. data/lib/inferno/repositories/repository.rb +13 -0
  55. data/lib/inferno/repositories/requests.rb +5 -4
  56. data/lib/inferno/repositories/results.rb +151 -3
  57. data/lib/inferno/repositories/session_data.rb +47 -0
  58. data/lib/inferno/repositories/test_runs.rb +81 -0
  59. data/lib/inferno/test_runner.rb +125 -31
  60. data/lib/inferno/utils/markdown_formatter.rb +15 -0
  61. data/lib/inferno/utils/middleware/request_logger.rb +16 -3
  62. data/lib/inferno/version.rb +1 -1
  63. data/lib/inferno.rb +4 -0
  64. data/spec/factories/header.rb +19 -0
  65. data/spec/factories/message.rb +17 -0
  66. data/spec/factories/request.rb +42 -0
  67. data/spec/factories/result.rb +45 -0
  68. data/spec/factories/test_run.rb +24 -0
  69. data/spec/factories/test_session.rb +11 -0
  70. data/spec/fixtures/basic_test_group.rb +9 -0
  71. data/spec/fixtures/basic_test_suite.rb +8 -0
  72. metadata +57 -5
  73. data/lib/inferno/dsl/fhir_manipulation.rb +0 -25
  74. data/lib/inferno/entities/test_input.rb +0 -20
@@ -0,0 +1,126 @@
1
+ module Inferno
2
+ module DSL
3
+ # This module contains the DSL for managing runnable configuration.
4
+ module Configurable
5
+ def self.extended(klass)
6
+ klass.extend Forwardable
7
+ klass.def_delegator 'self.class', :config
8
+ end
9
+
10
+ def config(new_configuration = {})
11
+ @config ||= Configuration.new
12
+
13
+ return @config if new_configuration.blank?
14
+
15
+ @config.apply(new_configuration)
16
+ end
17
+
18
+ # @api private
19
+ class Configuration
20
+ attr_accessor :configuration
21
+
22
+ def initialize(configuration = {})
23
+ self.configuration = configuration
24
+ end
25
+
26
+ def apply(new_configuration)
27
+ config_to_apply =
28
+ if new_configuration.is_a? Configuration
29
+ new_configuration.configuration
30
+ else
31
+ new_configuration
32
+ end
33
+
34
+ self.configuration = configuration.deep_merge(config_to_apply)
35
+ end
36
+
37
+ def options
38
+ configuration[:options] ||= {}
39
+ end
40
+
41
+ ### Input Configuration ###
42
+
43
+ def inputs
44
+ configuration[:inputs] ||= {}
45
+ end
46
+
47
+ def add_input(identifier, new_config = {})
48
+ existing_config = input_config(identifier) || {}
49
+ inputs[identifier] = default_input_config(identifier).merge(existing_config, new_config)
50
+ end
51
+
52
+ def default_input_config(identifier)
53
+ { name: identifier, type: 'text' }
54
+ end
55
+
56
+ def input_config_exists?(identifier)
57
+ inputs.key? identifier
58
+ end
59
+
60
+ def input_config(identifier)
61
+ inputs[identifier]
62
+ end
63
+
64
+ def input_name(identifier)
65
+ inputs.dig(identifier, :name) || identifier
66
+ end
67
+
68
+ ### Output Configuration ###
69
+
70
+ def outputs
71
+ configuration[:outputs] ||= {}
72
+ end
73
+
74
+ def add_output(identifier)
75
+ return if output_config_exists?(identifier)
76
+
77
+ outputs[identifier] = default_output_config(identifier)
78
+ end
79
+
80
+ def default_output_config(identifier)
81
+ { name: identifier }
82
+ end
83
+
84
+ def output_config_exists?(identifier)
85
+ outputs.key? identifier
86
+ end
87
+
88
+ def output_config(identifier)
89
+ outputs[identifier]
90
+ end
91
+
92
+ def output_name(identifier)
93
+ outputs.dig(identifier, :name) || identifier
94
+ end
95
+
96
+ ### Request Configuration ###
97
+
98
+ def requests
99
+ configuration[:requests] ||= {}
100
+ end
101
+
102
+ def add_request(identifier)
103
+ return if request_config_exists?(identifier)
104
+
105
+ requests[identifier] = default_request_config(identifier)
106
+ end
107
+
108
+ def default_request_config(identifier)
109
+ { name: identifier }
110
+ end
111
+
112
+ def request_config_exists?(identifier)
113
+ requests.key? identifier
114
+ end
115
+
116
+ def request_config(identifier)
117
+ requests[identifier]
118
+ end
119
+
120
+ def request_name(identifier)
121
+ requests.dig(identifier, :name) || identifier
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -69,12 +69,14 @@ module Inferno
69
69
  # @param client [Symbol]
70
70
  # @param name [Symbol] Name for this request to allow it to be used by
71
71
  # other tests
72
- # @param _options [Hash] TODO
72
+ # @option options [Hash] Input headers here - headers are optional and
73
+ # must be entered as the last piece of input to this method
73
74
  # @return [Inferno::Entities::Request]
74
- def fhir_operation(path, body: nil, client: :default, name: nil, **_options)
75
+ def fhir_operation(path, body: nil, client: :default, name: nil, **options)
75
76
  store_request('outgoing', name) do
76
77
  headers = fhir_client(client).fhir_headers
77
78
  headers.merge!('Content-Type' => 'application/fhir+json') if body.present?
79
+ headers.merge!(options[:headers]) if options[:headers].present?
78
80
  fhir_client(client).send(:post, path, body, headers)
79
81
  end
80
82
  end
@@ -14,6 +14,7 @@ module Inferno
14
14
  FHIR::Client.new(url).tap do |client|
15
15
  client.additional_headers = headers if headers
16
16
  client.default_json
17
+ client.set_bearer_token bearer_token if bearer_token
17
18
  end
18
19
  end
19
20
 
@@ -32,6 +33,21 @@ module Inferno
32
33
  end
33
34
  end
34
35
 
36
+ # Define the bearer token for a client. A string or symbol can be provided.
37
+ # A string is interpreted as a token. A symbol is interpreted as the name of
38
+ # an input to the Runnable.
39
+ #
40
+ # @param bearer_token [String, Symbol]
41
+ # @return [void]
42
+ def bearer_token(bearer_token = nil)
43
+ @bearer_token ||=
44
+ if bearer_token.is_a? Symbol
45
+ runnable.send(bearer_token)
46
+ else
47
+ bearer_token
48
+ end
49
+ end
50
+
35
51
  # Define custom headers for a client
36
52
  #
37
53
  # @param headers [Hash]
@@ -60,16 +60,17 @@ module Inferno
60
60
  # @param client [Symbol]
61
61
  # @param name [Symbol] Name for this request to allow it to be used by
62
62
  # other tests
63
- # @param _options [Hash] TODO
63
+ # @option options [Hash] Input headers here - headers are optional and
64
+ # must be entered as the last piece of input to this method
64
65
  # @return [Inferno::Entities::Request]
65
- def get(url = '', client: :default, name: nil, **_options)
66
+ def get(url = '', client: :default, name: nil, **options)
66
67
  store_request('outgoing', name) do
67
68
  client = http_client(client)
68
69
 
69
70
  if client
70
- client.get(url)
71
+ client.get(url, nil, options[:headers])
71
72
  elsif url.match?(%r{\Ahttps?://})
72
- Faraday.get(url)
73
+ Faraday.get(url, nil, options[:headers])
73
74
  else
74
75
  raise StandardError, 'Must use an absolute url or define an HTTP client with a base url'
75
76
  end
@@ -85,16 +86,17 @@ module Inferno
85
86
  # @param client [Symbol]
86
87
  # @param name [Symbol] Name for this request to allow it to be used by
87
88
  # other tests
88
- # @param _options [Hash] TODO
89
+ # @option options [Hash] Input headers here - headers are optional and
90
+ # must be entered as the last piece of input to this method
89
91
  # @return [Inferno::Entities::Request]
90
- def post(url = '', body: nil, client: :default, name: nil, **_options)
92
+ def post(url = '', body: nil, client: :default, name: nil, **options)
91
93
  store_request('outgoing', name) do
92
94
  client = http_client(client)
93
95
 
94
96
  if client
95
- client.post(url, body)
97
+ client.post(url, body, options[:headers])
96
98
  elsif url.match?(%r{\Ahttps?://})
97
- Faraday.post(url, body)
99
+ Faraday.post(url, body, options[:headers])
98
100
  else
99
101
  raise StandardError, 'Must use an absolute url or define an HTTP client with a base url'
100
102
  end
@@ -38,13 +38,14 @@ module Inferno
38
38
 
39
39
  # TODO: do a check in the test runner
40
40
  def named_request(name)
41
- requests.find { |request| request.name == name.to_sym }
41
+ requests.find { |request| request.name == self.class.config.request_name(name.to_sym) }
42
42
  end
43
43
 
44
44
  # @api private
45
45
  def store_request(direction, name = nil, &block)
46
46
  response = block.call
47
47
 
48
+ name = self.class.config.request_name(name)
48
49
  request =
49
50
  if response.is_a? FHIR::ClientReply
50
51
  Entities::Request.from_fhir_client_reply(
@@ -64,8 +65,9 @@ module Inferno
64
65
  def load_named_requests
65
66
  requests_repo = Inferno::Repositories::Requests.new
66
67
  self.class.named_requests_used.map do |request_name|
67
- request = requests_repo.find_named_request(test_session_id, request_name)
68
- raise StandardError, "Unable to find '#{request_name}' request" if request.nil?
68
+ request_alias = self.class.config.request_name(request_name)
69
+ request = requests_repo.find_named_request(test_session_id, request_alias)
70
+ raise StandardError, "Unable to find '#{request_alias}' request" if request.nil?
69
71
 
70
72
  requests << request
71
73
  end
@@ -84,16 +86,35 @@ module Inferno
84
86
 
85
87
  # Specify the named requests made by a test
86
88
  #
87
- # @param *names [Symbol] one or more Symbols
88
- def makes_request(*names)
89
- named_requests_made.concat(names)
89
+ # @param *identifiers [Symbol] one or more Symbols
90
+ def makes_request(*identifiers)
91
+ named_requests_made.concat(identifiers).uniq!
92
+ identifiers.each do |identifier|
93
+ config.add_request(identifier)
94
+ end
95
+ end
96
+
97
+ # Specify the name for a request received by a test
98
+ #
99
+ # @param *identifiers [Symbol] one or more Symbols
100
+ def receives_request(identifier)
101
+ config.add_request(identifier)
102
+ @incoming_request_name = identifier
103
+ end
104
+
105
+ # @api private
106
+ def incoming_request_name
107
+ @incoming_request_name
90
108
  end
91
109
 
92
110
  # Specify the named requests used by a test
93
111
  #
94
- # @param *names [Symbol] one or more Symbols
95
- def uses_request(*names)
96
- named_requests_used.concat(names)
112
+ # @param *identifiers [Symbol] one or more Symbols
113
+ def uses_request(*identifiers)
114
+ named_requests_used.concat(identifiers).uniq!
115
+ identifiers.each do |identifier|
116
+ config.add_request(identifier)
117
+ end
97
118
  end
98
119
  end
99
120
  end
@@ -49,6 +49,55 @@ module Inferno
49
49
  def omit_if(test, message = '')
50
50
  raise Exceptions::OmitException, message if test
51
51
  end
52
+
53
+ # Halt execution of the current test and wait for execution to resume.
54
+ #
55
+ # @see Inferno::DSL::Runnable#resume_test_route
56
+ # @example
57
+ # resume_test_route :get, '/launch' do
58
+ # request.query_parameters['iss']
59
+ # end
60
+ #
61
+ # test do
62
+ # input :issuer
63
+ # receives_request :launch
64
+ #
65
+ # run do
66
+ # wait(
67
+ # identifier: issuer,
68
+ # message: "Wating to receive a request with an issuer of #{issuer}"
69
+ # )
70
+ # end
71
+ # end
72
+ # @param identifier [String] An identifier which can uniquely identify
73
+ # this test run based on an incoming request. This is necessary so that
74
+ # the correct test run can be resumed.
75
+ # @param message [String]
76
+ # @param timeout [Integer] Number of seconds to wait for an incoming
77
+ # request
78
+ def wait(identifier:, message: '', timeout: 300)
79
+ identifier(identifier)
80
+ wait_timeout(timeout)
81
+
82
+ raise Exceptions::WaitException, message
83
+ end
84
+
85
+ def identifier(identifier = nil)
86
+ @identifier ||= identifier
87
+ end
88
+
89
+ def wait_timeout(timeout = nil)
90
+ @wait_timeout ||= timeout
91
+ end
92
+
93
+ # Halt execution of the current test. This provided for testing purposes
94
+ # and should not be used in real tests.
95
+ #
96
+ # @param message [String]
97
+ # @api private
98
+ def cancel(message = '')
99
+ raise Exceptions::CancelException, message
100
+ end
52
101
  end
53
102
  end
54
103
  end
@@ -0,0 +1,89 @@
1
+ require 'hanami-controller'
2
+
3
+ module Inferno
4
+ module DSL
5
+ # A base class for creating routes to resume test execution upon receiving
6
+ # an incoming request.
7
+ # @api private
8
+ # @see Inferno::DSL::Runnable#resume_test_route
9
+ class ResumeTestRoute
10
+ include Hanami::Action
11
+ include Import[
12
+ requests_repo: 'repositories.requests',
13
+ results_repo: 'repositories.results',
14
+ test_runs_repo: 'repositories.test_runs',
15
+ tests_repo: 'repositories.tests'
16
+ ]
17
+
18
+ def self.call(params)
19
+ new.call(params)
20
+ end
21
+
22
+ # The incoming request
23
+ #
24
+ # @return [Inferno::Entities::Request]
25
+ def request
26
+ @request ||= Inferno::Entities::Request.from_rack_env(@params.env)
27
+ end
28
+
29
+ # @api private
30
+ def test_run
31
+ @test_run ||=
32
+ test_runs_repo.find_latest_waiting_by_identifier(test_run_identifier)
33
+ end
34
+
35
+ # @api private
36
+ def waiting_result
37
+ @waiting_result ||= results_repo.find_waiting_result(test_run_id: test_run.id)
38
+ end
39
+
40
+ # @api private
41
+ def update_result
42
+ results_repo.pass_waiting_result(waiting_result.id)
43
+ end
44
+
45
+ # @api private
46
+ def persist_request
47
+ requests_repo.create(
48
+ request.to_hash.merge(
49
+ test_session_id: test_run.test_session_id,
50
+ result_id: waiting_result.id,
51
+ name: test.config.request_name(test.incoming_request_name)
52
+ )
53
+ )
54
+ end
55
+
56
+ # @api private
57
+ def redirect_route
58
+ "/test_sessions/#{test_run.test_session_id}##{waiting_group_id}"
59
+ end
60
+
61
+ # @api private
62
+ def test
63
+ @test ||= tests_repo.find(waiting_result.test_id)
64
+ end
65
+
66
+ # @api private
67
+ def waiting_group_id
68
+ test.parent.id
69
+ end
70
+
71
+ # @api private
72
+ def call(_params)
73
+ if test_run.nil?
74
+ status(500, "Unable to find test run with identifier '#{test_run_identifier}'.")
75
+ return
76
+ end
77
+
78
+ test_runs_repo.mark_as_no_longer_waiting(test_run.id)
79
+
80
+ update_result
81
+ persist_request
82
+
83
+ Jobs.perform(Jobs::ResumeTestRun, test_run.id)
84
+
85
+ redirect_to redirect_route
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,3 +1,7 @@
1
+ require_relative 'configurable'
2
+ require_relative 'resume_test_route'
3
+ require_relative '../utils/markdown_formatter'
4
+
1
5
  module Inferno
2
6
  module DSL
3
7
  # This module contains the DSL for defining child entities in the test
@@ -5,6 +9,8 @@ module Inferno
5
9
  module Runnable
6
10
  attr_accessor :parent
7
11
 
12
+ include Inferno::Utils::MarkdownFormatter
13
+
8
14
  # When a class (e.g. TestSuite/TestGroup) uses this module, set it up
9
15
  # so that subclassing it works correctly.
10
16
  # - add the subclass to the relevant repository when it is created
@@ -13,6 +19,7 @@ module Inferno
13
19
  # @api private
14
20
  def self.extended(extending_class)
15
21
  super
22
+ extending_class.extend Configurable
16
23
 
17
24
  extending_class.define_singleton_method(:inherited) do |subclass|
18
25
  copy_instance_variables(subclass)
@@ -37,11 +44,13 @@ module Inferno
37
44
  # @api private
38
45
  def copy_instance_variables(subclass)
39
46
  instance_variables.each do |variable|
40
- next if [:@id, :@groups, :@tests, :@parent, :@children].include?(variable)
47
+ next if [:@id, :@groups, :@tests, :@parent, :@children, :@test_count, :@config].include?(variable)
41
48
 
42
49
  subclass.instance_variable_set(variable, instance_variable_get(variable).dup)
43
50
  end
44
51
 
52
+ subclass.config(config)
53
+
45
54
  child_types.each do |child_type|
46
55
  new_children = send(child_type).map do |child|
47
56
  Class.new(child).tap do |subclass_child|
@@ -124,16 +133,16 @@ module Inferno
124
133
 
125
134
  # @api private
126
135
  def configure_child_class(klass, hash_args) # rubocop:disable Metrics/CyclomaticComplexity
127
- inputs.each do |input_definition|
128
- next if klass.inputs.include? input_definition
136
+ inputs.each do |name|
137
+ next if klass.inputs.any? { |klass_input_name| klass_input_name == name }
129
138
 
130
- klass.input input_definition
139
+ klass.input name
131
140
  end
132
141
 
133
- outputs.each do |output_definition|
134
- next if klass.outputs.include? output_definition
142
+ outputs.each do |output_name|
143
+ next if klass.outputs.include? output_name
135
144
 
136
- klass.output output_definition
145
+ klass.output output_name
137
146
  end
138
147
 
139
148
  new_fhir_client_definitions = klass.instance_variable_get(:@fhir_client_definitions) || {}
@@ -152,8 +161,14 @@ module Inferno
152
161
  end
153
162
  klass.instance_variable_set(:@http_client_definitions, new_http_client_definitions)
154
163
 
164
+ klass.config(config)
165
+
155
166
  hash_args.each do |key, value|
156
- klass.send(key, *value)
167
+ if value.is_a? Array
168
+ klass.send(key, *value)
169
+ else
170
+ klass.send(key, value)
171
+ end
157
172
  end
158
173
 
159
174
  klass.children.each do |child_class|
@@ -191,27 +206,48 @@ module Inferno
191
206
  def description(new_description = nil)
192
207
  return @description if new_description.nil?
193
208
 
194
- @description = new_description
209
+ @description = format_markdown(new_description)
195
210
  end
196
211
 
197
212
  # Define inputs
198
213
  #
199
- # @param inputs [Symbol]
214
+ # @param identifier [Symbol] identifier for the input
215
+ # @param other_identifiers [Symbol] array of symbols if specifying multiple inputs
216
+ # @param input_definition [Hash] options for input such as type, description, or title
217
+ # @option input_definition [String] :title Human readable title for input
218
+ # @option input_definition [String] :description Description for the input
219
+ # @option input_definition [String] :type text | textarea
220
+ # @option input_definition [String] :default The default value for the input
221
+ # @option input_definition [Boolean] :optional Set to true to not require input for test execution
200
222
  # @return [void]
201
223
  # @example
202
- # input :patient_id, :bearer_token
203
- def input(*input_definitions)
204
- inputs.concat(input_definitions)
224
+ # input :patientid, title: 'Patient ID', description: 'The ID of the patient being searched for',
225
+ # default: 'default_patient_id'
226
+ # @example
227
+ # input :textarea, title: 'Textarea Input Example', type: 'textarea', optional: true
228
+ def input(identifier, *other_identifiers, **input_definition)
229
+ if other_identifiers.present?
230
+ [identifier, *other_identifiers].compact.each do |input_identifier|
231
+ inputs << input_identifier
232
+ config.add_input(input_identifier)
233
+ end
234
+ else
235
+ inputs << identifier
236
+ config.add_input(identifier, input_definition)
237
+ end
205
238
  end
206
239
 
207
240
  # Define outputs
208
241
  #
209
- # @param output_definitions [Symbol]
242
+ # @param output_lists [Symbol]
210
243
  # @return [void]
211
244
  # @example
212
245
  # output :patient_id, :bearer_token
213
- def output(*output_definitions)
214
- outputs.concat(output_definitions)
246
+ def output(*output_list)
247
+ output_list.each do |output_identifier|
248
+ outputs << output_identifier
249
+ config.add_output(output_identifier)
250
+ end
215
251
  end
216
252
 
217
253
  # @api private
@@ -224,11 +260,20 @@ module Inferno
224
260
  @inputs ||= []
225
261
  end
226
262
 
263
+ def input_definitions
264
+ config.inputs.slice(*inputs)
265
+ end
266
+
267
+ def output_definitions
268
+ config.outputs.slice(*outputs)
269
+ end
270
+
227
271
  # @api private
228
272
  def outputs
229
273
  @outputs ||= []
230
274
  end
231
275
 
276
+ # @api private
232
277
  def child_types
233
278
  return [] if ancestors.include? Inferno::Entities::Test
234
279
  return [:groups] if ancestors.include? Inferno::Entities::TestSuite
@@ -236,6 +281,7 @@ module Inferno
236
281
  [:groups, :tests]
237
282
  end
238
283
 
284
+ # @api private
239
285
  def children
240
286
  @children ||= []
241
287
  end
@@ -245,6 +291,97 @@ module Inferno
245
291
 
246
292
  @validator_url = url
247
293
  end
294
+
295
+ # @api private
296
+ def suite
297
+ return self if ancestors.include? Inferno::Entities::TestSuite
298
+
299
+ parent.suite
300
+ end
301
+
302
+ # Create a route which will resume a test run when a request is received
303
+ #
304
+ # @see Inferno::DSL::Results#wait
305
+ # @example
306
+ # resume_test_route :get, '/launch' do
307
+ # request.query_parameters['iss']
308
+ # end
309
+ #
310
+ # test do
311
+ # input :issuer
312
+ # receives_request :launch
313
+ #
314
+ # run do
315
+ # wait(
316
+ # identifier: issuer,
317
+ # message: "Wating to receive a request with an issuer of #{issuer}"
318
+ # )
319
+ # end
320
+ # end
321
+ #
322
+ # @param method [Symbol] the HTTP request type (:get, :post, etc.) for the
323
+ # incoming request
324
+ # @param path [String] the path for this request. The route will be served
325
+ # with a prefix of `/custom/TEST_SUITE_ID` to prevent path conflicts.
326
+ # [Any of the path options available in Hanami
327
+ # Router](https://github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#a-beautiful-dsl)
328
+ # can be used here.
329
+ # @yield This method takes a block which must return the identifier
330
+ # defined when a test was set to wait for the test run that hit this
331
+ # route. The block has access to the `request` method which returns a
332
+ # {Inferno::DSL::Request} object with the information for the incoming
333
+ # request.
334
+ def resume_test_route(method, path, &block)
335
+ route_class = Class.new(ResumeTestRoute) do
336
+ define_method(:test_run_identifier, &block)
337
+ define_method(:request_name, -> { options[:name] })
338
+ end
339
+
340
+ route(method, path, route_class)
341
+ end
342
+
343
+ # Create a route to handle a request
344
+ #
345
+ # @param method [Symbol] the HTTP request type (:get, :post, etc.) for the
346
+ # incoming request. `:all` will accept all HTTP request types.
347
+ # @param path [String] the path for this request. The route will be served
348
+ # with a prefix of `/custom/TEST_SUITE_ID` to prevent path conflicts.
349
+ # [Any of the path options available in Hanami
350
+ # Router](https://github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#a-beautiful-dsl)
351
+ # can be used here.
352
+ # @param handler [#call] the route handler. This can be any Rack
353
+ # compatible object (e.g. a `Proc` object, a [Sinatra
354
+ # app](http://sinatrarb.com/)) as described in the [Hanami Router
355
+ # documentation.](https://github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#mount-rack-applications)
356
+ def route(method, path, handler)
357
+ Inferno.routes << { method: method, path: path, handler: handler, suite: suite }
358
+ end
359
+
360
+ def test_count
361
+ @test_count ||= children&.reduce(0) { |sum, child| sum + child.test_count } || 0
362
+ end
363
+
364
+ def required_inputs(prior_outputs = [])
365
+ required_inputs = inputs.select do |input|
366
+ !input_definitions[input][:optional] && !prior_outputs.include?(input)
367
+ end
368
+ required_inputs.map! { |input_identifier| input_definitions[input_identifier][:name] }
369
+ children_required_inputs = children.flat_map { |child| child.required_inputs(prior_outputs) }
370
+ prior_outputs.concat(outputs)
371
+ (required_inputs + children_required_inputs).flatten.uniq
372
+ end
373
+
374
+ def missing_inputs(submitted_inputs)
375
+ submitted_inputs = [] if submitted_inputs.nil?
376
+
377
+ required_inputs.map(&:to_s) - submitted_inputs.map { |input| input[:name] }
378
+ end
379
+
380
+ def user_runnable?
381
+ @user_runnable ||= parent.nil? ||
382
+ !parent.respond_to?(:run_as_group?) ||
383
+ (parent.user_runnable? && !parent.run_as_group?)
384
+ end
248
385
  end
249
386
  end
250
387
  end