inferno_core 0.0.1

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