inferno_core 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno.rb +4 -0
  3. data/lib/inferno/apps/web/controllers/test_runs/create.rb +17 -10
  4. data/lib/inferno/apps/web/controllers/test_runs/show.rb +10 -0
  5. data/lib/inferno/apps/web/controllers/test_sessions/create.rb +1 -0
  6. data/lib/inferno/apps/web/controllers/test_sessions/last_test_run.rb +22 -0
  7. data/lib/inferno/apps/web/controllers/test_sessions/results/index.rb +6 -1
  8. data/lib/inferno/apps/web/controllers/test_sessions/session_data/index.rb +21 -0
  9. data/lib/inferno/apps/web/index.html.erb +44 -0
  10. data/lib/inferno/apps/web/router.rb +15 -0
  11. data/lib/inferno/apps/web/serializers/request.rb +1 -0
  12. data/lib/inferno/apps/web/serializers/result.rb +8 -0
  13. data/lib/inferno/apps/web/serializers/session_data.rb +10 -0
  14. data/lib/inferno/apps/web/serializers/test.rb +1 -3
  15. data/lib/inferno/apps/web/serializers/test_group.rb +2 -3
  16. data/lib/inferno/apps/web/serializers/test_run.rb +1 -0
  17. data/lib/inferno/apps/web/serializers/test_session.rb +1 -1
  18. data/lib/inferno/apps/web/serializers/test_suite.rb +1 -0
  19. data/lib/inferno/config/application.rb +4 -2
  20. data/lib/inferno/config/boot.rb +2 -0
  21. data/lib/inferno/config/boot/db.rb +10 -1
  22. data/lib/inferno/config/boot/sidekiq.rb +11 -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.rb +1 -3
  30. data/lib/inferno/dsl/fhir_client_builder.rb +16 -0
  31. data/lib/inferno/dsl/request_storage.rb +12 -0
  32. data/lib/inferno/dsl/results.rb +49 -0
  33. data/lib/inferno/dsl/resume_test_route.rb +89 -0
  34. data/lib/inferno/dsl/runnable.rb +96 -7
  35. data/lib/inferno/entities.rb +1 -1
  36. data/lib/inferno/entities/header.rb +7 -7
  37. data/lib/inferno/entities/message.rb +8 -6
  38. data/lib/inferno/entities/request.rb +40 -14
  39. data/lib/inferno/entities/result.rb +34 -18
  40. data/lib/inferno/entities/session_data.rb +33 -0
  41. data/lib/inferno/entities/test.rb +20 -7
  42. data/lib/inferno/entities/test_run.rb +13 -6
  43. data/lib/inferno/entities/test_session.rb +8 -8
  44. data/lib/inferno/exceptions.rb +12 -0
  45. data/lib/inferno/jobs.rb +16 -0
  46. data/lib/inferno/jobs/execute_test_run.rb +14 -0
  47. data/lib/inferno/jobs/resume_test_run.rb +14 -0
  48. data/lib/inferno/public/bundle.js +1 -1
  49. data/lib/inferno/repositories/repository.rb +13 -0
  50. data/lib/inferno/repositories/requests.rb +5 -4
  51. data/lib/inferno/repositories/results.rb +151 -3
  52. data/lib/inferno/repositories/session_data.rb +47 -0
  53. data/lib/inferno/repositories/test_runs.rb +66 -0
  54. data/lib/inferno/test_runner.rb +121 -29
  55. data/lib/inferno/utils/middleware/request_logger.rb +16 -3
  56. data/lib/inferno/version.rb +1 -1
  57. data/spec/factories/header.rb +19 -0
  58. data/spec/factories/message.rb +17 -0
  59. data/spec/factories/request.rb +35 -0
  60. data/spec/factories/result.rb +45 -0
  61. data/spec/factories/test_run.rb +24 -0
  62. data/spec/factories/test_session.rb +11 -0
  63. data/spec/fixtures/basic_test_group.rb +9 -0
  64. data/spec/fixtures/basic_test_suite.rb +8 -0
  65. metadata +139 -89
  66. data/lib/inferno/dsl/fhir_manipulation.rb +0 -25
  67. data/lib/inferno/entities/test_input.rb +0 -20
@@ -0,0 +1,9 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table :requests_results do
4
+ # using results_id instead of result_id to avoid ambiguous column error
5
+ foreign_key :results_id, :results, index: true, type: String, null: false
6
+ foreign_key :requests_id, :requests, index: true, type: String, null: false
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ Sequel.migration do
2
+ change do
3
+ add_index :results, [:test_run_id, :updated_at], concurrently: true
4
+ end
5
+ end
@@ -0,0 +1,154 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:schema_info) do
4
+ Integer :version, :default=>0, :null=>false
5
+ end
6
+
7
+ create_table(:test_sessions) do
8
+ String :id, :size=>255, :null=>false
9
+ String :test_suite_id, :size=>255
10
+ DateTime :created_at, :null=>false
11
+ DateTime :updated_at, :null=>false
12
+
13
+ primary_key [:id]
14
+ end
15
+
16
+ create_table(:session_data, :ignore_index_errors=>true) do
17
+ String :id, :size=>255, :null=>false
18
+ foreign_key :test_session_id, :test_sessions, :type=>String, :size=>255
19
+ String :name, :size=>255
20
+ String :value, :text=>true
21
+
22
+ index [:id], :unique=>true
23
+ index [:test_session_id]
24
+ index [:test_session_id, :name], :unique=>true
25
+ end
26
+
27
+ create_table(:test_runs, :ignore_index_errors=>true) do
28
+ String :id, :size=>255, :null=>false
29
+ String :status, :size=>255
30
+ foreign_key :test_session_id, :test_sessions, :type=>String, :size=>255
31
+ String :test_suite_id, :size=>255
32
+ String :test_group_id, :size=>255
33
+ String :test_id, :size=>255
34
+ DateTime :created_at, :null=>false
35
+ DateTime :updated_at, :null=>false
36
+ String :identifier, :text=>true
37
+ DateTime :wait_timeout
38
+
39
+ primary_key [:id]
40
+
41
+ index [:status, :identifier, :wait_timeout, :updated_at]
42
+ index [:test_group_id]
43
+ index [:test_id]
44
+ index [:test_session_id]
45
+ index [:test_session_id, :status]
46
+ index [:test_suite_id]
47
+ end
48
+
49
+ create_table(:results, :ignore_index_errors=>true) do
50
+ String :id, :size=>255, :null=>false
51
+ foreign_key :test_run_id, :test_runs, :type=>String, :size=>255
52
+ foreign_key :test_session_id, :test_sessions, :type=>String, :size=>255
53
+ String :result, :size=>255
54
+ String :result_message, :size=>255
55
+ String :test_suite_id, :size=>255
56
+ String :test_group_id, :size=>255
57
+ String :test_id, :size=>255
58
+ DateTime :created_at, :null=>false
59
+ DateTime :updated_at, :null=>false
60
+ String :input_json, :text=>true
61
+ String :output_json, :text=>true
62
+
63
+ primary_key [:id]
64
+
65
+ index [:test_run_id]
66
+ index [:test_run_id, :updated_at]
67
+ index [:test_session_id]
68
+ index [:test_session_id, :test_group_id]
69
+ index [:test_session_id, :test_id]
70
+ index [:test_session_id, :test_suite_id]
71
+ end
72
+
73
+ create_table(:messages, :ignore_index_errors=>true) do
74
+ primary_key :index
75
+ String :id, :size=>255, :null=>false
76
+ foreign_key :result_id, :results, :type=>String, :size=>255
77
+ String :type, :size=>255
78
+ String :message, :size=>255
79
+ DateTime :created_at, :null=>false
80
+ DateTime :updated_at, :null=>false
81
+
82
+ index [:id]
83
+ index [:result_id]
84
+ end
85
+
86
+ create_table(:requests, :ignore_index_errors=>true) do
87
+ primary_key :index
88
+ String :id, :size=>255, :null=>false
89
+ String :verb, :size=>255
90
+ String :url, :size=>255
91
+ String :direction, :size=>255
92
+ Integer :status
93
+ String :name, :size=>255
94
+ String :request_body, :text=>true
95
+ String :response_body, :text=>true
96
+ foreign_key :result_id, :results, :type=>String, :size=>255
97
+ foreign_key :test_session_id, :test_sessions, :type=>String, :size=>255
98
+ String :"[:test_session_id, :name]"
99
+ DateTime :created_at, :null=>false
100
+ DateTime :updated_at, :null=>false
101
+
102
+ index [:id]
103
+ index [:result_id]
104
+ index [:test_session_id]
105
+ end
106
+
107
+ create_table(:result_outputs, :ignore_index_errors=>true) do
108
+ String :id, :size=>255, :null=>false
109
+ foreign_key :result_id, :results, :type=>String, :size=>255
110
+ String :test_output_id, :size=>255
111
+ String :value, :size=>255
112
+ DateTime :created_at, :null=>false
113
+ DateTime :updated_at, :null=>false
114
+
115
+ primary_key [:id]
116
+
117
+ index [:result_id]
118
+ end
119
+
120
+ create_table(:result_prompt_values, :ignore_index_errors=>true) do
121
+ String :id, :size=>255, :null=>false
122
+ foreign_key :result_id, :results, :type=>String, :size=>255
123
+ String :test_prompt_id, :size=>255, :null=>false
124
+ String :value, :size=>255, :null=>false
125
+ DateTime :created_at, :null=>false
126
+ DateTime :updated_at, :null=>false
127
+
128
+ primary_key [:id]
129
+
130
+ index [:result_id]
131
+ end
132
+
133
+ create_table(:headers, :ignore_index_errors=>true) do
134
+ String :id, :size=>255, :null=>false
135
+ foreign_key :request_id, :requests, :type=>String, :size=>255
136
+ String :type, :size=>255
137
+ String :name, :size=>255
138
+ String :value, :size=>255
139
+ DateTime :created_at, :null=>false
140
+ DateTime :updated_at, :null=>false
141
+
142
+ index [:id]
143
+ index [:request_id]
144
+ end
145
+
146
+ create_table(:requests_results, :ignore_index_errors=>true) do
147
+ foreign_key :results_id, :results, :type=>String, :size=>255, :null=>false
148
+ foreign_key :requests_id, :requests, :type=>String, :size=>255, :null=>false
149
+
150
+ index [:requests_id]
151
+ index [:results_id]
152
+ end
153
+ end
154
+ end
data/lib/inferno/dsl.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require_relative 'dsl/assertions'
2
2
  require_relative 'dsl/fhir_client'
3
- require_relative 'dsl/fhir_manipulation'
4
3
  require_relative 'dsl/fhir_validation'
5
4
  require_relative 'dsl/http_client'
6
5
  require_relative 'dsl/results'
@@ -14,8 +13,7 @@ module Inferno
14
13
  FHIRClient,
15
14
  HTTPClient,
16
15
  Results,
17
- FHIRValidation,
18
- FHIRManipulation
16
+ FHIRValidation
19
17
  ].freeze
20
18
 
21
19
  EXTENDABLE_DSL_MODULES = [
@@ -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]
@@ -89,6 +89,18 @@ module Inferno
89
89
  named_requests_made.concat(names)
90
90
  end
91
91
 
92
+ # Specify the name for a request received by a test
93
+ #
94
+ # @param *names [Symbol] one or more Symbols
95
+ def receives_request(name)
96
+ @incoming_request_name = name
97
+ end
98
+
99
+ # @api private
100
+ def incoming_request_name
101
+ @incoming_request_name
102
+ end
103
+
92
104
  # Specify the named requests used by a test
93
105
  #
94
106
  # @param *names [Symbol] one or more Symbols
@@ -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.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,5 @@
1
+ require_relative 'resume_test_route'
2
+
1
3
  module Inferno
2
4
  module DSL
3
5
  # This module contains the DSL for defining child entities in the test
@@ -37,7 +39,7 @@ module Inferno
37
39
  # @api private
38
40
  def copy_instance_variables(subclass)
39
41
  instance_variables.each do |variable|
40
- next if [:@id, :@groups, :@tests, :@parent, :@children].include?(variable)
42
+ next if [:@id, :@groups, :@tests, :@parent, :@children, :@test_count].include?(variable)
41
43
 
42
44
  subclass.instance_variable_set(variable, instance_variable_get(variable).dup)
43
45
  end
@@ -125,9 +127,9 @@ module Inferno
125
127
  # @api private
126
128
  def configure_child_class(klass, hash_args) # rubocop:disable Metrics/CyclomaticComplexity
127
129
  inputs.each do |input_definition|
128
- next if klass.inputs.include? input_definition
130
+ next if klass.inputs.any? { |input| input[:name] == input_definition[:name] }
129
131
 
130
- klass.input input_definition
132
+ klass.input input_definition[:name], input_definition
131
133
  end
132
134
 
133
135
  outputs.each do |output_definition|
@@ -196,12 +198,28 @@ module Inferno
196
198
 
197
199
  # Define inputs
198
200
  #
199
- # @param inputs [Symbol]
201
+ # @param name [Symbol] name of the input
202
+ # @param other_names [Symbol] array of symbols if specifying multiple inputs
203
+ # @param input_definition [Hash] options for input such as type, description, or title
204
+ # @option input_definition [String] :title Human readable title for input
205
+ # @option input_definition [String] :description Description for the input
206
+ # @option input_definition [String] :type text | textarea
207
+ # @option input_definition [String] :default The default value for the input
200
208
  # @return [void]
201
209
  # @example
202
- # input :patient_id, :bearer_token
203
- def input(*input_definitions)
204
- inputs.concat(input_definitions)
210
+ # input :patientid, title: 'Patient ID', description: 'The ID of the patient being searched for',
211
+ # default: 'default_patient_id'
212
+ # @example
213
+ # input :textarea, title: 'Textarea Input Example', type: 'textarea'
214
+ def input(name, *other_names, **input_definition)
215
+ if other_names.present?
216
+ [name, *other_names].each do |input_name|
217
+ inputs.push({ name: input_name, title: nil, description: nil, type: 'text' })
218
+ end
219
+ else
220
+ input_definition[:type] = 'text' unless input_definition.key? :type
221
+ inputs.push({ name: name }.merge(input_definition))
222
+ end
205
223
  end
206
224
 
207
225
  # Define outputs
@@ -229,6 +247,7 @@ module Inferno
229
247
  @outputs ||= []
230
248
  end
231
249
 
250
+ # @api private
232
251
  def child_types
233
252
  return [] if ancestors.include? Inferno::Entities::Test
234
253
  return [:groups] if ancestors.include? Inferno::Entities::TestSuite
@@ -236,6 +255,7 @@ module Inferno
236
255
  [:groups, :tests]
237
256
  end
238
257
 
258
+ # @api private
239
259
  def children
240
260
  @children ||= []
241
261
  end
@@ -245,6 +265,75 @@ module Inferno
245
265
 
246
266
  @validator_url = url
247
267
  end
268
+
269
+ # @api private
270
+ def suite
271
+ return self if ancestors.include? Inferno::Entities::TestSuite
272
+
273
+ parent.suite
274
+ end
275
+
276
+ # Create a route which will resume a test run when a request is received
277
+ #
278
+ # @see Inferno::DSL::Results#wait
279
+ # @example
280
+ # resume_test_route :get, '/launch' do
281
+ # request.query_parameters['iss']
282
+ # end
283
+ #
284
+ # test do
285
+ # input :issuer
286
+ # receives_request :launch
287
+ #
288
+ # run do
289
+ # wait(
290
+ # identifier: issuer,
291
+ # message: "Wating to receive a request with an issuer of #{issuer}"
292
+ # )
293
+ # end
294
+ # end
295
+ #
296
+ # @param method [Symbol] the HTTP request type (:get, :post, etc.) for the
297
+ # incoming request
298
+ # @param path [String] the path for this request. The route will be served
299
+ # with a prefix of `/custom/TEST_SUITE_ID` to prevent path conflicts.
300
+ # [Any of the path options available in Hanami
301
+ # Router](https://github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#a-beautiful-dsl)
302
+ # can be used here.
303
+ # @yield This method takes a block which must return the identifier
304
+ # defined when a test was set to wait for the test run that hit this
305
+ # route. The block has access to the `request` method which returns a
306
+ # {Inferno::DSL::Request} object with the information for the incoming
307
+ # request.
308
+ def resume_test_route(method, path, &block)
309
+ route_class = Class.new(ResumeTestRoute) do
310
+ define_method(:test_run_identifier, &block)
311
+ define_method(:request_name, -> { options[:name] })
312
+ end
313
+
314
+ route(method, path, route_class)
315
+ end
316
+
317
+ # Create a route to handle a request
318
+ #
319
+ # @param method [Symbol] the HTTP request type (:get, :post, etc.) for the
320
+ # incoming request. `:all` will accept all HTTP request types.
321
+ # @param path [String] the path for this request. The route will be served
322
+ # with a prefix of `/custom/TEST_SUITE_ID` to prevent path conflicts.
323
+ # [Any of the path options available in Hanami
324
+ # Router](https://github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#a-beautiful-dsl)
325
+ # can be used here.
326
+ # @param handler [#call] the route handler. This can be any Rack
327
+ # compatible object (e.g. a `Proc` object, a [Sinatra
328
+ # app](http://sinatrarb.com/)) as described in the [Hanami Router
329
+ # documentation.](https://github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#mount-rack-applications)
330
+ def route(method, path, handler)
331
+ Inferno.routes << { method: method, path: path, handler: handler, suite: suite }
332
+ end
333
+
334
+ def test_count
335
+ @test_count ||= children&.reduce(0) { |sum, child| sum + child.test_count } || 0
336
+ end
248
337
  end
249
338
  end
250
339
  end