inferno_core 0.0.5 → 0.0.8.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/bin/inferno +7 -0
  3. data/lib/inferno/apps/cli/console.rb +12 -0
  4. data/lib/inferno/apps/cli/main.rb +18 -0
  5. data/lib/inferno/apps/cli/migration.rb +14 -0
  6. data/lib/inferno/apps/cli.rb +8 -0
  7. data/lib/inferno/apps/web/controllers/test_runs/create.rb +15 -2
  8. data/lib/inferno/apps/web/index.html.erb +1 -0
  9. data/lib/inferno/apps/web/serializers/hash_value_extractor.rb +11 -0
  10. data/lib/inferno/apps/web/serializers/test.rb +3 -4
  11. data/lib/inferno/apps/web/serializers/test_group.rb +4 -6
  12. data/lib/inferno/config/application.rb +4 -0
  13. data/lib/inferno/config/boot/db.rb +1 -9
  14. data/lib/inferno/config/boot/logging.rb +2 -0
  15. data/lib/inferno/config/boot/suites.rb +4 -6
  16. data/lib/inferno/db/migrations/001_create_initial_structure.rb +1 -1
  17. data/lib/inferno/db/schema.rb +1 -1
  18. data/lib/inferno/dsl/assertions.rb +85 -0
  19. data/lib/inferno/dsl/configurable.rb +126 -0
  20. data/lib/inferno/dsl/fhir_client.rb +22 -16
  21. data/lib/inferno/dsl/fhir_client_builder.rb +3 -3
  22. data/lib/inferno/dsl/fhir_validation.rb +105 -1
  23. data/lib/inferno/dsl/http_client.rb +14 -12
  24. data/lib/inferno/dsl/http_client_builder.rb +3 -3
  25. data/lib/inferno/dsl/request_storage.rb +26 -17
  26. data/lib/inferno/dsl/results.rb +1 -1
  27. data/lib/inferno/dsl/resume_test_route.rb +10 -10
  28. data/lib/inferno/dsl/runnable.rb +104 -38
  29. data/lib/inferno/entities/header.rb +14 -7
  30. data/lib/inferno/entities/message.rb +16 -8
  31. data/lib/inferno/entities/request.rb +34 -21
  32. data/lib/inferno/entities/result.rb +36 -29
  33. data/lib/inferno/entities/session_data.rb +12 -6
  34. data/lib/inferno/entities/test.rb +29 -2
  35. data/lib/inferno/entities/test_group.rb +8 -0
  36. data/lib/inferno/entities/test_run.rb +29 -6
  37. data/lib/inferno/entities/test_session.rb +16 -10
  38. data/lib/inferno/exceptions.rb +12 -0
  39. data/lib/inferno/public/217.bundle.js +1 -1
  40. data/lib/inferno/public/bundle.js +154 -1
  41. data/lib/inferno/public/bundle.js.LICENSE.txt +15 -0
  42. data/lib/inferno/repositories/in_memory_repository.rb +1 -1
  43. data/lib/inferno/repositories/results.rb +1 -1
  44. data/lib/inferno/repositories/test_runs.rb +15 -0
  45. data/lib/inferno/spec_support.rb +1 -1
  46. data/lib/inferno/test_runner.rb +21 -19
  47. data/lib/inferno/utils/markdown_formatter.rb +15 -0
  48. data/lib/inferno/utils/middleware/request_logger.rb +9 -3
  49. data/lib/inferno/utils/migration.rb +17 -0
  50. data/lib/inferno/version.rb +1 -1
  51. data/lib/inferno.rb +0 -4
  52. data/spec/factories/request.rb +14 -7
  53. metadata +46 -10
  54. data/bin/inferno-console +0 -8
@@ -1,38 +1,113 @@
1
1
  module Inferno
2
2
  module DSL
3
+ # This module contains the methods needed to configure a validator to
4
+ # perform validation of FHIR resources. The actual validation is performed
5
+ # by an external FHIR validation service. Tests will typically rely on
6
+ # `assert_valid_resource` for validation rather than directly calling
7
+ # methods on a validator.
8
+ #
9
+ # @example
10
+ #
11
+ # validator do
12
+ # url 'http://example.com/validator'
13
+ # exclude_message { |message| message.type == 'info' }
14
+ # perform_additional_validation do |resource, profile_url|
15
+ # if something_is_wrong
16
+ # { type: 'error', message: 'something is wrong' }
17
+ # else
18
+ # { type: 'info', message: 'everything is ok' }
19
+ # end
20
+ # end
21
+ # end
3
22
  module FHIRValidation
4
23
  def self.included(klass)
5
24
  klass.extend ClassMethods
6
25
  end
7
26
 
27
+ # Perform validation, and add validation messages to the runnable
28
+ #
29
+ # @param resource [FHIR::Model]
30
+ # @param profile_url [String]
31
+ # @param validator [Symbol] the name of the validator to use
32
+ # @return [Boolean] whether the resource is valid
8
33
  def resource_is_valid?(resource: self.resource, profile_url: nil, validator: :default)
9
34
  find_validator(validator).resource_is_valid?(resource, profile_url, self)
10
35
  end
11
36
 
37
+ # Find a particular validator. Looks through a runnable's parents up to
38
+ # the suite to find a validator with a particular name
12
39
  def find_validator(validator_name)
13
40
  self.class.find_validator(validator_name)
14
41
  end
15
42
 
16
43
  class Validator
44
+ # @private
17
45
  def initialize(&block)
18
46
  instance_eval(&block)
19
47
  end
20
48
 
49
+ # @private
21
50
  def default_validator_url
22
51
  ENV.fetch('VALIDATOR_URL')
23
52
  end
24
53
 
54
+ # Set the url of the validator service
55
+ #
56
+ # @param url [String]
25
57
  def url(validator_url = nil)
26
58
  @url = validator_url if validator_url
27
59
 
28
60
  @url
29
61
  end
30
62
 
63
+ # @private
64
+ def additional_validations
65
+ @additional_validations ||= []
66
+ end
67
+
68
+ # Perform validation steps in addition to FHIR validation.
69
+ #
70
+ # @example
71
+ # perform_additional_validation do |resource, profile_url|
72
+ # if something_is_wrong
73
+ # { type: 'error', message: 'something is wrong' }
74
+ # else
75
+ # { type: 'info', message: 'everything is ok' }
76
+ # end
77
+ # end
78
+ # @yieldparam resource [FHIR::Model] the resource being validated
79
+ # @yieldparam profile_url [String] the profile the resource is being
80
+ # validated against
81
+ # @yieldreturn [Array<Hash<Symbol, String>>,Hash<Symbol, String>] The
82
+ # block should return a Hash or an Array of Hashes if any validation
83
+ # messages should be added. The Hash must contain two keys: `:type`
84
+ # and `:message`. `:type` can have a value of `'info'`, `'warning'`,
85
+ # or `'error'`. A type of `'error'` means the resource is invalid.
86
+ # `:message` contains the message string itself.
87
+ def perform_additional_validation(&block)
88
+ additional_validations << block
89
+ end
90
+
91
+ # @private
92
+ def additional_validation_messages(resource, profile_url)
93
+ additional_validations
94
+ .flat_map { |step| step.call(resource, profile_url) }
95
+ .select { |message| message.is_a? Hash }
96
+ end
97
+
98
+ # Filter out unwanted validation messages
99
+ #
100
+ # @example
101
+ # validator do
102
+ # exclude_message { |message| message.type == 'info' }
103
+ # end
104
+ # @yieldparam message [Inferno::Entities::Message]
31
105
  def exclude_message(&block)
32
106
  @exclude_message = block if block_given?
33
107
  @exclude_message
34
108
  end
35
109
 
110
+ # @see Inferno::DSL::FHIRValidation#resource_is_valid?
36
111
  def resource_is_valid?(resource, profile_url, runnable)
37
112
  profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url
38
113
 
@@ -40,6 +115,8 @@ module Inferno
40
115
 
41
116
  message_hashes = outcome.issue&.map { |issue| message_hash_from_issue(issue) } || []
42
117
 
118
+ message_hashes.concat(additional_validation_messages(resource, profile_url))
119
+
43
120
  filter_messages(message_hashes)
44
121
 
45
122
  message_hashes
@@ -47,10 +124,12 @@ module Inferno
47
124
  .none? { |message_hash| message_hash[:type] == 'error' }
48
125
  end
49
126
 
127
+ # @private
50
128
  def filter_messages(message_hashes)
51
129
  message_hashes.reject! { |message| exclude_message.call(Entities::Message.new(message)) } if exclude_message
52
130
  end
53
131
 
132
+ # @private
54
133
  def message_hash_from_issue(issue)
55
134
  {
56
135
  type: issue_severity(issue),
@@ -58,6 +137,7 @@ module Inferno
58
137
  }
59
138
  end
60
139
 
140
+ # @private
61
141
  def issue_severity(issue)
62
142
  case issue.severity
63
143
  when 'warning'
@@ -69,6 +149,7 @@ module Inferno
69
149
  end
70
150
  end
71
151
 
152
+ # @private
72
153
  def issue_message(issue)
73
154
  location = if issue.respond_to?(:expression)
74
155
  issue.expression&.join(', ')
@@ -79,6 +160,11 @@ module Inferno
79
160
  "#{location}: #{issue&.details&.text}"
80
161
  end
81
162
 
163
+ # Post a resource to the validation service for validating.
164
+ #
165
+ # @param resource [FHIR::Model]
166
+ # @param profile_url [String]
167
+ # @return [String] the body of the validation response
82
168
  def validate(resource, profile_url)
83
169
  RestClient.post(
84
170
  "#{url}/validate",
@@ -89,15 +175,33 @@ module Inferno
89
175
  end
90
176
 
91
177
  module ClassMethods
92
- # @api private
178
+ # @private
93
179
  def fhir_validators
94
180
  @fhir_validators ||= {}
95
181
  end
96
182
 
183
+ # Define a validator
184
+ # @example
185
+ # validator do
186
+ # url 'http://example.com/validator'
187
+ # exclude_message { |message| message.type == 'info' }
188
+ # perform_additional_validation do |resource, profile_url|
189
+ # if something_is_wrong
190
+ # { type: 'error', message: 'something is wrong' }
191
+ # else
192
+ # { type: 'info', message: 'everything is ok' }
193
+ # end
194
+ # end
195
+ # end
196
+ #
197
+ # @param name [Symbol] the name of the validator, only needed if you are
198
+ # using multiple validators
97
199
  def validator(name = :default, &block)
98
200
  fhir_validators[name] = Inferno::DSL::FHIRValidation::Validator.new(&block)
99
201
  end
100
202
 
203
+ # Find a particular validator. Looks through a runnable's parents up to
204
+ # the suite to find a validator with a particular name
101
205
  def find_validator(validator_name)
102
206
  validator = fhir_validators[validator_name] || parent&.find_validator(validator_name)
103
207
 
@@ -16,7 +16,7 @@ module Inferno
16
16
  # # performs a GET to https://example.com
17
17
  # get
18
18
  # # performs a GET to https://example.com/abc
19
- # get('abc')
19
+ # get('abc')
20
20
  #
21
21
  # request # the most recent request
22
22
  # response # the most recent response
@@ -27,7 +27,7 @@ module Inferno
27
27
  # @see Inferno::FHIRClientBuilder Documentation for the client
28
28
  # configuration DSL
29
29
  module HTTPClient
30
- # @api private
30
+ # @private
31
31
  def self.included(klass)
32
32
  klass.extend ClassMethods
33
33
  klass.include RequestStorage
@@ -47,7 +47,7 @@ module Inferno
47
47
  http_clients[client] = HTTPClientBuilder.new.build(self, definition)
48
48
  end
49
49
 
50
- # @api private
50
+ # @private
51
51
  def http_clients
52
52
  @http_clients ||= {}
53
53
  end
@@ -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
@@ -102,7 +104,7 @@ module Inferno
102
104
  end
103
105
 
104
106
  module ClassMethods
105
- # @api private
107
+ # @private
106
108
  def http_client_definitions
107
109
  @http_client_definitions ||= {}
108
110
  end
@@ -4,7 +4,7 @@ module Inferno
4
4
  class HTTPClientBuilder
5
5
  attr_accessor :runnable
6
6
 
7
- # @api private
7
+ # @private
8
8
  def build(runnable, block)
9
9
  self.runnable = runnable
10
10
  instance_exec(self, &block)
@@ -38,14 +38,14 @@ module Inferno
38
38
  @headers ||= headers
39
39
  end
40
40
 
41
- # @api private
41
+ # @private
42
42
  def method_missing(name, *args, &block)
43
43
  return runnable.call(name, *args, &block) if runnable.respond_to? name
44
44
 
45
45
  super
46
46
  end
47
47
 
48
- # @api private
48
+ # @private
49
49
  def respond_to_missing?(name)
50
50
  runnable.respond_to?(name) || super
51
51
  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
- # @api private
44
+ # @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(
@@ -60,52 +61,60 @@ module Inferno
60
61
  request
61
62
  end
62
63
 
63
- # @api private
64
+ # @private
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
72
74
  end
73
75
 
74
76
  module ClassMethods
75
- # @api private
77
+ # @private
76
78
  def named_requests_made
77
79
  @named_requests_made ||= []
78
80
  end
79
81
 
80
- # @api private
82
+ # @private
81
83
  def named_requests_used
82
84
  @named_requests_used ||= []
83
85
  end
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 request identifiers
90
+ def makes_request(*identifiers)
91
+ named_requests_made.concat(identifiers).uniq!
92
+ identifiers.each do |identifier|
93
+ config.add_request(identifier)
94
+ end
90
95
  end
91
96
 
92
97
  # Specify the name for a request received by a test
93
98
  #
94
- # @param *names [Symbol] one or more Symbols
95
- def receives_request(name)
96
- @incoming_request_name = name
99
+ # @param identifier [Symbol]
100
+ def receives_request(identifier)
101
+ config.add_request(identifier)
102
+ @incoming_request_name = identifier
97
103
  end
98
104
 
99
- # @api private
105
+ # @private
100
106
  def incoming_request_name
101
107
  @incoming_request_name
102
108
  end
103
109
 
104
110
  # Specify the named requests used by a test
105
111
  #
106
- # @param *names [Symbol] one or more Symbols
107
- def uses_request(*names)
108
- named_requests_used.concat(names)
112
+ # @param identifiers [Symbol] one or more request identifiers
113
+ def uses_request(*identifiers)
114
+ named_requests_used.concat(identifiers).uniq!
115
+ identifiers.each do |identifier|
116
+ config.add_request(identifier)
117
+ end
109
118
  end
110
119
  end
111
120
  end
@@ -94,7 +94,7 @@ module Inferno
94
94
  # and should not be used in real tests.
95
95
  #
96
96
  # @param message [String]
97
- # @api private
97
+ # @private
98
98
  def cancel(message = '')
99
99
  raise Exceptions::CancelException, message
100
100
  end
@@ -4,7 +4,7 @@ module Inferno
4
4
  module DSL
5
5
  # A base class for creating routes to resume test execution upon receiving
6
6
  # an incoming request.
7
- # @api private
7
+ # @private
8
8
  # @see Inferno::DSL::Runnable#resume_test_route
9
9
  class ResumeTestRoute
10
10
  include Hanami::Action
@@ -26,49 +26,49 @@ module Inferno
26
26
  @request ||= Inferno::Entities::Request.from_rack_env(@params.env)
27
27
  end
28
28
 
29
- # @api private
29
+ # @private
30
30
  def test_run
31
31
  @test_run ||=
32
32
  test_runs_repo.find_latest_waiting_by_identifier(test_run_identifier)
33
33
  end
34
34
 
35
- # @api private
35
+ # @private
36
36
  def waiting_result
37
37
  @waiting_result ||= results_repo.find_waiting_result(test_run_id: test_run.id)
38
38
  end
39
39
 
40
- # @api private
40
+ # @private
41
41
  def update_result
42
42
  results_repo.pass_waiting_result(waiting_result.id)
43
43
  end
44
44
 
45
- # @api private
45
+ # @private
46
46
  def persist_request
47
47
  requests_repo.create(
48
48
  request.to_hash.merge(
49
49
  test_session_id: test_run.test_session_id,
50
50
  result_id: waiting_result.id,
51
- name: test.incoming_request_name
51
+ name: test.config.request_name(test.incoming_request_name)
52
52
  )
53
53
  )
54
54
  end
55
55
 
56
- # @api private
56
+ # @private
57
57
  def redirect_route
58
58
  "/test_sessions/#{test_run.test_session_id}##{waiting_group_id}"
59
59
  end
60
60
 
61
- # @api private
61
+ # @private
62
62
  def test
63
63
  @test ||= tests_repo.find(waiting_result.test_id)
64
64
  end
65
65
 
66
- # @api private
66
+ # @private
67
67
  def waiting_group_id
68
68
  test.parent.id
69
69
  end
70
70
 
71
- # @api private
71
+ # @private
72
72
  def call(_params)
73
73
  if test_run.nil?
74
74
  status(500, "Unable to find test run with identifier '#{test_run_identifier}'.")