inferno_core 0.0.2 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|