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.
- checksums.yaml +4 -4
- data/lib/inferno/apps/web/controllers/test_runs/create.rb +30 -10
- data/lib/inferno/apps/web/controllers/test_runs/show.rb +10 -0
- data/lib/inferno/apps/web/controllers/test_sessions/create.rb +1 -0
- data/lib/inferno/apps/web/controllers/test_sessions/last_test_run.rb +22 -0
- data/lib/inferno/apps/web/controllers/test_sessions/results/index.rb +6 -1
- data/lib/inferno/apps/web/controllers/test_sessions/session_data/index.rb +21 -0
- data/lib/inferno/apps/web/router.rb +15 -0
- data/lib/inferno/apps/web/serializers/hash_value_extractor.rb +11 -0
- data/lib/inferno/apps/web/serializers/request.rb +1 -0
- data/lib/inferno/apps/web/serializers/result.rb +8 -0
- data/lib/inferno/apps/web/serializers/session_data.rb +10 -0
- data/lib/inferno/apps/web/serializers/test.rb +3 -6
- data/lib/inferno/apps/web/serializers/test_group.rb +5 -8
- data/lib/inferno/apps/web/serializers/test_run.rb +1 -0
- data/lib/inferno/apps/web/serializers/test_session.rb +1 -1
- data/lib/inferno/apps/web/serializers/test_suite.rb +1 -0
- data/lib/inferno/config/application.rb +5 -2
- data/lib/inferno/config/boot/db.rb +10 -1
- data/lib/inferno/config/boot/sidekiq.rb +11 -0
- data/lib/inferno/config/boot/suites.rb +4 -6
- data/lib/inferno/config/boot.rb +2 -0
- data/lib/inferno/db/migrations/001_create_initial_structure.rb +0 -21
- data/lib/inferno/db/migrations/002_add_wait_support.rb +7 -0
- data/lib/inferno/db/migrations/003_update_session_data.rb +18 -0
- data/lib/inferno/db/migrations/004_add_request_results_table.rb +9 -0
- data/lib/inferno/db/migrations/005_add_updated_at_index_to_results.rb +5 -0
- data/lib/inferno/db/schema.rb +154 -0
- data/lib/inferno/dsl/assertions.rb +20 -0
- data/lib/inferno/dsl/configurable.rb +126 -0
- data/lib/inferno/dsl/fhir_client.rb +4 -2
- data/lib/inferno/dsl/fhir_client_builder.rb +16 -0
- data/lib/inferno/dsl/http_client.rb +10 -8
- data/lib/inferno/dsl/request_storage.rb +30 -9
- data/lib/inferno/dsl/results.rb +49 -0
- data/lib/inferno/dsl/resume_test_route.rb +89 -0
- data/lib/inferno/dsl/runnable.rb +153 -16
- data/lib/inferno/dsl.rb +1 -3
- data/lib/inferno/entities/header.rb +7 -7
- data/lib/inferno/entities/message.rb +8 -6
- data/lib/inferno/entities/request.rb +42 -16
- data/lib/inferno/entities/result.rb +34 -18
- data/lib/inferno/entities/session_data.rb +33 -0
- data/lib/inferno/entities/test.rb +35 -8
- data/lib/inferno/entities/test_group.rb +8 -0
- data/lib/inferno/entities/test_run.rb +13 -6
- data/lib/inferno/entities/test_session.rb +8 -8
- data/lib/inferno/entities.rb +1 -1
- data/lib/inferno/exceptions.rb +24 -0
- data/lib/inferno/jobs/execute_test_run.rb +14 -0
- data/lib/inferno/jobs/resume_test_run.rb +14 -0
- data/lib/inferno/jobs.rb +16 -0
- data/lib/inferno/public/bundle.js +1 -1
- data/lib/inferno/repositories/repository.rb +13 -0
- data/lib/inferno/repositories/requests.rb +5 -4
- data/lib/inferno/repositories/results.rb +151 -3
- data/lib/inferno/repositories/session_data.rb +47 -0
- data/lib/inferno/repositories/test_runs.rb +81 -0
- data/lib/inferno/test_runner.rb +125 -31
- data/lib/inferno/utils/markdown_formatter.rb +15 -0
- data/lib/inferno/utils/middleware/request_logger.rb +16 -3
- data/lib/inferno/version.rb +1 -1
- data/lib/inferno.rb +4 -0
- data/spec/factories/header.rb +19 -0
- data/spec/factories/message.rb +17 -0
- data/spec/factories/request.rb +42 -0
- data/spec/factories/result.rb +45 -0
- data/spec/factories/test_run.rb +24 -0
- data/spec/factories/test_session.rb +11 -0
- data/spec/fixtures/basic_test_group.rb +9 -0
- data/spec/fixtures/basic_test_suite.rb +8 -0
- metadata +57 -5
- data/lib/inferno/dsl/fhir_manipulation.rb +0 -25
- 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
|
-
# @
|
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, **
|
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
|
-
# @
|
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, **
|
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
|
-
# @
|
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, **
|
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
|
-
|
68
|
-
|
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 *
|
88
|
-
def makes_request(*
|
89
|
-
named_requests_made.concat(
|
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 *
|
95
|
-
def uses_request(*
|
96
|
-
named_requests_used.concat(
|
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
|
data/lib/inferno/dsl/results.rb
CHANGED
@@ -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
|
data/lib/inferno/dsl/runnable.rb
CHANGED
@@ -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 |
|
128
|
-
next if klass.inputs.
|
136
|
+
inputs.each do |name|
|
137
|
+
next if klass.inputs.any? { |klass_input_name| klass_input_name == name }
|
129
138
|
|
130
|
-
klass.input
|
139
|
+
klass.input name
|
131
140
|
end
|
132
141
|
|
133
|
-
outputs.each do |
|
134
|
-
next if klass.outputs.include?
|
142
|
+
outputs.each do |output_name|
|
143
|
+
next if klass.outputs.include? output_name
|
135
144
|
|
136
|
-
klass.output
|
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
|
-
|
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
|
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 :
|
203
|
-
|
204
|
-
|
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
|
242
|
+
# @param output_lists [Symbol]
|
210
243
|
# @return [void]
|
211
244
|
# @example
|
212
245
|
# output :patient_id, :bearer_token
|
213
|
-
def output(*
|
214
|
-
|
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
|