inferno_core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/bin/inferno-console +8 -0
  4. data/lib/inferno.rb +15 -0
  5. data/lib/inferno/apps/web/application.rb +12 -0
  6. data/lib/inferno/apps/web/controllers/controller.rb +34 -0
  7. data/lib/inferno/apps/web/controllers/requests/show.rb +16 -0
  8. data/lib/inferno/apps/web/controllers/test_runs/create.rb +39 -0
  9. data/lib/inferno/apps/web/controllers/test_runs/results/index.rb +18 -0
  10. data/lib/inferno/apps/web/controllers/test_runs/show.rb +16 -0
  11. data/lib/inferno/apps/web/controllers/test_sessions/create.rb +26 -0
  12. data/lib/inferno/apps/web/controllers/test_sessions/results/index.rb +17 -0
  13. data/lib/inferno/apps/web/controllers/test_sessions/show.rb +16 -0
  14. data/lib/inferno/apps/web/controllers/test_suites/index.rb +13 -0
  15. data/lib/inferno/apps/web/controllers/test_suites/show.rb +16 -0
  16. data/lib/inferno/apps/web/router.rb +27 -0
  17. data/lib/inferno/apps/web/serializers/header.rb +12 -0
  18. data/lib/inferno/apps/web/serializers/input.rb +16 -0
  19. data/lib/inferno/apps/web/serializers/message.rb +10 -0
  20. data/lib/inferno/apps/web/serializers/request.rb +26 -0
  21. data/lib/inferno/apps/web/serializers/result.rb +21 -0
  22. data/lib/inferno/apps/web/serializers/serializer.rb +16 -0
  23. data/lib/inferno/apps/web/serializers/test.rb +17 -0
  24. data/lib/inferno/apps/web/serializers/test_group.rb +23 -0
  25. data/lib/inferno/apps/web/serializers/test_run.rb +19 -0
  26. data/lib/inferno/apps/web/serializers/test_session.rb +17 -0
  27. data/lib/inferno/apps/web/serializers/test_suite.rb +18 -0
  28. data/lib/inferno/config/application.rb +22 -0
  29. data/lib/inferno/config/boot.rb +5 -0
  30. data/lib/inferno/config/boot/db.rb +23 -0
  31. data/lib/inferno/config/boot/logging.rb +16 -0
  32. data/lib/inferno/config/boot/suites.rb +27 -0
  33. data/lib/inferno/config/boot/web.rb +17 -0
  34. data/lib/inferno/db/migrations/001_create_initial_structure.rb +165 -0
  35. data/lib/inferno/dsl.rb +35 -0
  36. data/lib/inferno/dsl/assertions.rb +93 -0
  37. data/lib/inferno/dsl/fhir_client.rb +152 -0
  38. data/lib/inferno/dsl/fhir_client_builder.rb +56 -0
  39. data/lib/inferno/dsl/fhir_manipulation.rb +25 -0
  40. data/lib/inferno/dsl/fhir_validation.rb +111 -0
  41. data/lib/inferno/dsl/http_client.rb +122 -0
  42. data/lib/inferno/dsl/http_client_builder.rb +54 -0
  43. data/lib/inferno/dsl/request_storage.rb +101 -0
  44. data/lib/inferno/dsl/results.rb +54 -0
  45. data/lib/inferno/dsl/runnable.rb +250 -0
  46. data/lib/inferno/entities.rb +19 -0
  47. data/lib/inferno/entities/attributes.rb +9 -0
  48. data/lib/inferno/entities/entity.rb +15 -0
  49. data/lib/inferno/entities/header.rb +42 -0
  50. data/lib/inferno/entities/message.rb +22 -0
  51. data/lib/inferno/entities/request.rb +166 -0
  52. data/lib/inferno/entities/result.rb +49 -0
  53. data/lib/inferno/entities/test.rb +173 -0
  54. data/lib/inferno/entities/test_group.rb +87 -0
  55. data/lib/inferno/entities/test_input.rb +20 -0
  56. data/lib/inferno/entities/test_run.rb +63 -0
  57. data/lib/inferno/entities/test_session.rb +26 -0
  58. data/lib/inferno/entities/test_suite.rb +73 -0
  59. data/lib/inferno/exceptions.rb +42 -0
  60. data/lib/inferno/public/217.bundle.js +1 -0
  61. data/lib/inferno/public/assets.json +6 -0
  62. data/lib/inferno/public/bundle.js +2 -0
  63. data/lib/inferno/public/bundle.js.LICENSE.txt +65 -0
  64. data/lib/inferno/public/e09b16f5cb645eb05f90c8f38f3409fb.png +0 -0
  65. data/lib/inferno/public/favicon.ico +0 -0
  66. data/lib/inferno/public/logo192.png +0 -0
  67. data/lib/inferno/public/logo512.png +0 -0
  68. data/lib/inferno/repositories.rb +27 -0
  69. data/lib/inferno/repositories/headers.rb +22 -0
  70. data/lib/inferno/repositories/in_memory_repository.rb +41 -0
  71. data/lib/inferno/repositories/messages.rb +35 -0
  72. data/lib/inferno/repositories/repository.rb +106 -0
  73. data/lib/inferno/repositories/requests.rb +89 -0
  74. data/lib/inferno/repositories/results.rb +72 -0
  75. data/lib/inferno/repositories/test_groups.rb +9 -0
  76. data/lib/inferno/repositories/test_runs.rb +46 -0
  77. data/lib/inferno/repositories/test_sessions.rb +56 -0
  78. data/lib/inferno/repositories/test_suites.rb +9 -0
  79. data/lib/inferno/repositories/tests.rb +9 -0
  80. data/lib/inferno/repositories/validate_runnable_reference.rb +42 -0
  81. data/lib/inferno/spec_support.rb +9 -0
  82. data/lib/inferno/test_runner.rb +81 -0
  83. data/lib/inferno/utils/middleware/request_logger.rb +55 -0
  84. data/lib/inferno/version.rb +3 -0
  85. data/spec/support/factory_bot.rb +21 -0
  86. metadata +514 -0
@@ -0,0 +1,19 @@
1
+ require_relative 'entities/attributes'
2
+ require_relative 'entities/entity'
3
+ require_relative 'entities/header'
4
+ require_relative 'entities/message'
5
+ require_relative 'entities/request'
6
+ require_relative 'entities/result'
7
+ require_relative 'entities/test'
8
+ require_relative 'entities/test_group'
9
+ require_relative 'entities/test_input'
10
+ require_relative 'entities/test_run'
11
+ require_relative 'entities/test_session'
12
+ require_relative 'entities/test_suite'
13
+
14
+ module Inferno
15
+ # Entities are domain objects whose identity is based on an `id`. Entities
16
+ # don't know anything about persistence, which is handled by Repositories.
17
+ module Entities
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Inferno
2
+ module Entities
3
+ module Attributes
4
+ def self.included(klass)
5
+ klass.attr_accessor(*klass::ATTRIBUTES)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Inferno
2
+ module Entities
3
+ class Entity
4
+ def initialize(params, attributes)
5
+ attributes.each { |name| instance_variable_set("@#{name}", params[name]) }
6
+ end
7
+
8
+ def to_hash
9
+ self.class::ATTRIBUTES.each_with_object({}) do |attribute, hash|
10
+ hash[attribute] = send(attribute)
11
+ end.compact
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ module Inferno
2
+ module Entities
3
+ # A `Header` represents an HTTP request/response header
4
+ #
5
+ # @attr_reader [String] id of the header
6
+ # @attr_reader [String] request_id index of the HTTP request
7
+ # @attr_reader [String] name header name
8
+ # @attr_reader [String] value header value
9
+ # @attr_reader [String] type request/response
10
+ # @attr_reader [Time] created_at
11
+ # @attr_reader [Time] updated_at
12
+ class Header < Entity
13
+ ATTRIBUTES = [:id, :request_id, :name, :type, :value, :created_at, :updated_at].freeze
14
+
15
+ include Inferno::Entities::Attributes
16
+
17
+ def initialize(params)
18
+ super(params, ATTRIBUTES)
19
+ end
20
+
21
+ def request?
22
+ type == 'request'
23
+ end
24
+
25
+ def response?
26
+ type == 'response'
27
+ end
28
+
29
+ def to_hash
30
+ {
31
+ id: id,
32
+ request_id: request_id,
33
+ type: type,
34
+ name: name,
35
+ value: value,
36
+ created_at: created_at,
37
+ updated_at: updated_at
38
+ }.compact
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module Inferno
2
+ module Entities
3
+ # A `Message` represents a message generated during a test.
4
+ #
5
+ # @attr_reader [String] id of the message
6
+ # @attr_reader [String] index of the message. Used for ordering.
7
+ # @attr_reader [String] result_id
8
+ # @attr_reader [Inferno::Entities::Result] result
9
+ # @attr_reader [String] type
10
+ # @attr_reader [String] message
11
+ class Message < Entity
12
+ ATTRIBUTES = [:id, :index, :message, :result_id, :result, :type, :created_at, :updated_at].freeze
13
+ TYPES = ['error', 'warning', 'info'].freeze
14
+
15
+ include Inferno::Entities::Attributes
16
+
17
+ def initialize(params)
18
+ super(params, ATTRIBUTES)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,166 @@
1
+ module Inferno
2
+ module Entities
3
+ # A `Request` represents a request and response issued during a test.
4
+ #
5
+ # @attr_reader [String] id of the request
6
+ # @attr_reader [String] index of the request. Used for ordering.
7
+ # @attr_reader [String] verb http verb
8
+ # @attr_reader [String] url request url
9
+ # @attr_reader [String] direction incoming/outgoing
10
+ # @attr_reader [String] name name for the request
11
+ # @attr_reader [String] status http response status code
12
+ # @attr_reader [String] request_body body of the http request
13
+ # @attr_reader [String] response_body body of the http response
14
+ # @attr_reader [Array<Inferno::Entities::Header>] headers http
15
+ # request/response headers
16
+ # @attr_reader [String] result_id id of the result for this request
17
+ # @attr_reader [String] test_session_id id of the test session for this request
18
+ # @attr_reader [Time] created_at creation timestamp
19
+ # @attr_reader [Time] updated_at update timestamp
20
+ class Request < Entity
21
+ ATTRIBUTES = [
22
+ :id, :index, :verb, :url, :direction, :name, :status,
23
+ :request_body, :response_body, :result_id, :test_session_id, :created_at,
24
+ :updated_at, :headers
25
+ ].freeze
26
+ SUMMARY_FIELDS = [
27
+ :id, :index, :url, :verb, :direction, :name, :status, :result_id, :created_at, :updated_at
28
+ ].freeze
29
+
30
+ include Attributes
31
+
32
+ def initialize(params)
33
+ super(params, ATTRIBUTES - [:headers, :name])
34
+
35
+ @name = params[:name]&.to_sym
36
+ @headers = params[:headers]&.map { |header| header.is_a?(Hash) ? Header.new(header) : header } || []
37
+ end
38
+
39
+ # Find a response header
40
+ #
41
+ # @param name [String] the header name
42
+ # @return [Inferno::Entities::RequestHeader, nil]
43
+ def response_header(name)
44
+ response_headers.find { |header| header.name == name.downcase }
45
+ end
46
+
47
+ # Find a request header
48
+ #
49
+ # @param name [String] the header name
50
+ # @return [Inferno::Entities::RequestHeader, nil]
51
+ def request_header(name)
52
+ request_headers.find { |header| header.name == name.downcase }
53
+ end
54
+
55
+ # All of the request headers
56
+ #
57
+ # @return [Array<Inferno::Entities::RequestHeader>]
58
+ def request_headers
59
+ headers.select(&:request?)
60
+ end
61
+
62
+ # All of the response headers
63
+ #
64
+ # @return [Array<Inferno::Entities::RequestHeader>]
65
+ def response_headers
66
+ headers.select(&:response?)
67
+ end
68
+
69
+ # Return a hash of the request parameters
70
+ #
71
+ # @return [Hash]
72
+ def request
73
+ {
74
+ verb: verb,
75
+ url: url,
76
+ headers: request_headers,
77
+ body: request_body
78
+ }
79
+ end
80
+
81
+ # Return a hash of the response parameters
82
+ #
83
+ # @return [Hash]
84
+ def response
85
+ {
86
+ status: status,
87
+ headers: response_headers,
88
+ body: response_body
89
+ }
90
+ end
91
+
92
+ # @api private
93
+ def to_hash
94
+ {
95
+ id: id,
96
+ verb: verb,
97
+ url: url,
98
+ direction: direction,
99
+ status: status,
100
+ name: name,
101
+ request_body: request_body,
102
+ response_body: response_body,
103
+ result_id: result_id,
104
+ test_session_id: test_session_id,
105
+ request_headers: request_headers.map(&:to_hash),
106
+ response_headers: response_headers.map(&:to_hash),
107
+ created_at: created_at,
108
+ updated_at: updated_at
109
+ }.compact
110
+ end
111
+
112
+ # Return the FHIR resource from the response body.
113
+ #
114
+ # @return [FHIR::Model]
115
+ def resource
116
+ FHIR.from_contents(response_body)
117
+ end
118
+
119
+ class << self
120
+ # @api private
121
+ def from_http_response(response, test_session_id:, direction: 'outgoing', name: nil)
122
+ request_headers =
123
+ response.env.request_headers
124
+ .map { |header_name, value| Header.new(name: header_name.downcase, value: value, type: 'request') }
125
+ response_headers =
126
+ response.headers
127
+ .map { |header_name, value| Header.new(name: header_name.downcase, value: value, type: 'response') }
128
+
129
+ new(
130
+ verb: response.env.method,
131
+ url: response.env.url.to_s,
132
+ direction: direction,
133
+ name: name,
134
+ status: response.status,
135
+ request_body: response.env.request_body,
136
+ response_body: response.body,
137
+ test_session_id: test_session_id,
138
+ headers: request_headers + response_headers
139
+ )
140
+ end
141
+
142
+ # @api private
143
+ def from_fhir_client_reply(reply, test_session_id:, direction: 'outgoing', name: nil)
144
+ request = reply.request
145
+ response = reply.response
146
+ request_headers = request[:headers]
147
+ .map { |header_name, value| Header.new(name: header_name.downcase, value: value, type: 'request') }
148
+ response_headers = response[:headers]
149
+ .map { |header_name, value| Header.new(name: header_name.downcase, value: value, type: 'response') }
150
+
151
+ new(
152
+ verb: request[:method],
153
+ url: request[:url],
154
+ direction: direction,
155
+ name: name,
156
+ status: response[:code].to_i,
157
+ request_body: request[:payload],
158
+ response_body: response[:body],
159
+ test_session_id: test_session_id,
160
+ headers: request_headers + response_headers
161
+ )
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,49 @@
1
+ module Inferno
2
+ module Entities
3
+ # A `Result` represents the result of running a `Test`, `TestGroup`,
4
+ # or `TestSuite`
5
+ #
6
+ # @attr_reader [String] id id of the session
7
+ # @attr_reader [Time] created_at creation timestamp
8
+ # @attr_reader [Time] updated_at update timestamp
9
+ # @attr_reader [String] reference_type type of entity this result belongs to
10
+ # (`Test`, `TestGroup`, or `TestSuite`)
11
+ # @attr_reader [String, nil] test_id id of the `Test` this result belongs to
12
+ # @attr_reader [Test, nil] test the `Test` this result belongs to
13
+ # @attr_reader [String, nil] test_group_id id of the `TestGroup` this result belongs to
14
+ # @attr_reader [TestGroup, nil] test_group the `TestGroup` this result belongs to
15
+ # @attr_reader [String, nil] test_suite_id id of the `TestSuite` this result belongs to
16
+ # @attr_reader [TestSuite, nil] test_suite the `TestSuite` this result belongs to
17
+ # @attr_reader [String] result the result (`pass`, `fail`, `skip`, `omit`,
18
+ # `error`, `running`, `wait`, `cancel`)
19
+ # @attr_reader [String] result_message summary message for this result
20
+ # @attr_reader [String] test_run_id the `TestRun` this result belongs to
21
+ # @attr_reader [String] test_session_id the `TestSession` this result
22
+ # belongs to
23
+ # @attr_reader [Array<Inferno::Entities::Message>] messages additional
24
+ # messages for this result
25
+ # @attr_reader [Array<Inferno::Entities::Request>] request_summaries
26
+ # summaries of the requests associated with this result
27
+ class Result < Entity
28
+ ATTRIBUTES = [
29
+ :id, :created_at, :updated_at, :test_id, :test, :test_group_id,
30
+ :test_group, :test_suite_id, :test_suite, :test_run_id,
31
+ :test_session_id, :result, :result_message, :messages, :requests
32
+ ].freeze
33
+ RESULT_OPTIONS = ['cancel', 'wait', 'running', 'error', 'fail', 'skip', 'omit', 'pass'].freeze
34
+
35
+ include Inferno::Entities::Attributes
36
+
37
+ def initialize(params)
38
+ super(params, ATTRIBUTES - [:messages, :requests])
39
+
40
+ @messages = (params[:messages] || []).map { |message| Message.new(message) }
41
+ @requests = (params[:requests] || []).map { |request| Request.new(request) }
42
+ end
43
+
44
+ def runnable
45
+ test || test_group || test_suite
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,173 @@
1
+ require_relative '../dsl'
2
+ require_relative '../repositories/tests'
3
+
4
+ module Inferno
5
+ module Entities
6
+ class Test
7
+ extend Forwardable
8
+ include DSL
9
+
10
+ def_delegators 'self.class', :title, :id, :block, :inputs, :outputs
11
+
12
+ attr_accessor :result_message
13
+ attr_reader :inputs, :test_session_id
14
+
15
+ def initialize(**params)
16
+ @inputs = params[:inputs]
17
+ @test_session_id = params[:test_session_id]
18
+ end
19
+
20
+ def messages
21
+ @messages ||= []
22
+ end
23
+
24
+ def add_message(type, message)
25
+ messages << { type: type.to_s, message: message }
26
+ end
27
+
28
+ # Set output values. Once set, these values will be available to any
29
+ # subsequent tests.
30
+ #
31
+ # @param outputs [Hash]
32
+ # @return [void]
33
+ # @example
34
+ # output(patient_id: '5', bearer_token: 'ABC')
35
+ def output(outputs)
36
+ outputs.each do |key, value|
37
+ send("#{key}=", value)
38
+ end
39
+ end
40
+
41
+ # Add an informational message to the results of a test. If passed a
42
+ # block, a failed assertion will become an info message and test execution
43
+ # will continue.
44
+ #
45
+ # @param message [String]
46
+ # @return [void]
47
+ # @example
48
+ # # Add an info message
49
+ # info 'This message will be added to the test results'
50
+ #
51
+ # # The message for the failed assertion will be treated as an info
52
+ # # message. Test exection will continue.
53
+ # info { assert false == true }
54
+ def info(message = nil)
55
+ unless block_given?
56
+ add_message('info', message) unless message.nil?
57
+ return
58
+ end
59
+
60
+ yield
61
+ rescue Exceptions::AssertionException => e
62
+ add_message('info', e.message)
63
+ end
64
+
65
+ # Add a warning message to the results of a test. If passed a block, a
66
+ # failed assertion will become a warning message and test execution will
67
+ # continue.
68
+ #
69
+ # @param message [String]
70
+ # @return [void]
71
+ # @example
72
+ # # Add a warning message
73
+ # warning 'This message will be added to the test results'
74
+ #
75
+ # # The message for the failed assertion will be treated as a warning
76
+ # # message. Test exection will continue.
77
+ # warning { assert false == true }
78
+ def warning(message = nil)
79
+ unless block_given?
80
+ add_message('warning', message) unless message.nil?
81
+ return
82
+ end
83
+
84
+ yield
85
+ rescue Exceptions::AssertionException => e
86
+ add_message('warning', e.message)
87
+ end
88
+
89
+ def method_missing(name, *args, &block)
90
+ parent_instance = self.class.parent&.new
91
+ if parent_instance.respond_to?(name)
92
+ parent_instance.send(name, *args, &block)
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+ def respond_to_missing?(name, _include_private = false)
99
+ self.class.parent&.new&.respond_to?(name)
100
+ end
101
+
102
+ class << self
103
+ # Define inputs for this Test
104
+ #
105
+ # @param inputs [Symbol]
106
+ # @return [void]
107
+ # @example
108
+ # input :patient_id, :bearer_token
109
+ def input(*input_definitions)
110
+ super
111
+
112
+ input_definitions.each do |input|
113
+ attr_reader input
114
+ end
115
+ end
116
+
117
+ # Define outputs for this Test
118
+ #
119
+ # @param output_definitions [Symbol]
120
+ # @return [void]
121
+ # @example
122
+ # output :patient_id, :bearer_token
123
+ def output(*output_definitions)
124
+ super
125
+
126
+ output_definitions.each do |output|
127
+ attr_accessor output
128
+ end
129
+ end
130
+
131
+ def repository
132
+ Inferno::Repositories::Tests.new
133
+ end
134
+
135
+ def block(&block)
136
+ return @block unless block_given?
137
+
138
+ @block = block
139
+ end
140
+
141
+ alias run block
142
+
143
+ def default_id
144
+ return name if name.present?
145
+
146
+ suffix = parent ? (parent.tests.find_index(self) + 1).to_s.rjust(2, '0') : SecureRandom.uuid
147
+ "Test#{suffix}"
148
+ end
149
+
150
+ def reference_hash
151
+ {
152
+ test_id: id
153
+ }
154
+ end
155
+
156
+ def method_missing(name, *args, &block)
157
+ parent_instance = parent&.new
158
+ if parent_instance.respond_to?(name)
159
+ parent_instance.send(name, *args, &block)
160
+ else
161
+ super
162
+ end
163
+ end
164
+
165
+ def respond_to_missing?(name, _include_private = false)
166
+ parent&.new&.respond_to?(name)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ Test = Entities::Test
173
+ end