inferno_core 0.0.8 → 0.1.0.pre

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.
@@ -0,0 +1,119 @@
1
+ require_relative '../entities/attributes'
2
+
3
+ module Inferno
4
+ module DSL
5
+ class OAuthCredentials
6
+ ATTRIBUTES = [
7
+ :access_token,
8
+ :refresh_token,
9
+ :token_url,
10
+ :client_id,
11
+ :client_secret,
12
+ :token_retrieval_time,
13
+ :expires_in,
14
+ :name
15
+ ].freeze
16
+
17
+ include Entities::Attributes
18
+
19
+ attr_accessor :client
20
+
21
+ def initialize(raw_attributes_hash)
22
+ attributes_hash = raw_attributes_hash.symbolize_keys
23
+
24
+ invalid_keys = attributes_hash.keys - ATTRIBUTES
25
+
26
+ raise Exceptions::UnknownAttributeException.new(invalid_keys, self.class) if invalid_keys.present?
27
+
28
+ attributes_hash.each do |name, value|
29
+ value = DateTime.parse(value) if name == :token_retrieval_time && value.is_a?(String)
30
+
31
+ instance_variable_set(:"@#{name}", value)
32
+ end
33
+
34
+ self.token_retrieval_time = DateTime.now if token_retrieval_time.blank?
35
+ end
36
+
37
+ # @api private
38
+ def to_hash
39
+ self.class::ATTRIBUTES.each_with_object({}) do |attribute, hash|
40
+ value = send(attribute)
41
+ next if value.nil?
42
+
43
+ value = token_retrieval_time.iso8601 if attribute == :token_retrieval_time
44
+
45
+ hash[attribute] = value
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ def to_s
51
+ JSON.generate(to_hash)
52
+ end
53
+
54
+ # @api private
55
+ def add_to_client(client)
56
+ client.oauth_credentials = self
57
+ self.client = client
58
+
59
+ return unless access_token.present?
60
+
61
+ client.set_bearer_token(access_token)
62
+ end
63
+
64
+ # @api private
65
+ def need_to_refresh?
66
+ return false if access_token.blank? || refresh_token.blank?
67
+
68
+ return true if expires_in.blank?
69
+
70
+ token_retrieval_time.to_i + expires_in - DateTime.now.to_i < 60
71
+ end
72
+
73
+ # @api private
74
+ def able_to_refresh?
75
+ refresh_token.present? && token_url.present?
76
+ end
77
+
78
+ # @api private
79
+ def confidential_client?
80
+ client_id.present? && client_secret.present?
81
+ end
82
+
83
+ # @api private
84
+ def oauth2_refresh_params
85
+ {
86
+ 'grant_type' => 'refresh_token',
87
+ 'refresh_token' => refresh_token
88
+ }
89
+ end
90
+
91
+ # @api private
92
+ def oauth2_refresh_headers
93
+ base_headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
94
+
95
+ return base_headers unless confidential_client?
96
+
97
+ credentials = "#{client_id}:#{client_secret}"
98
+
99
+ base_headers.merge(
100
+ 'Authorization' => "Basic #{Base64.strict_encode64(credentials)}"
101
+ )
102
+ end
103
+
104
+ def update_from_response_body(request)
105
+ token_response_body = JSON.parse(request.response_body)
106
+
107
+ expires_in = token_response_body['expires_in'].is_a?(Numeric) ? token_response_body['expires_in'] : nil
108
+
109
+ self.access_token = token_response_body['access_token']
110
+ self.refresh_token = token_response_body['refresh_token']
111
+ self.expires_in = expires_in
112
+ self.token_retrieval_time = DateTime.now
113
+
114
+ add_to_client(client)
115
+ self
116
+ end
117
+ end
118
+ end
119
+ end
@@ -229,12 +229,14 @@ module Inferno
229
229
  # @param input_definition [Hash] options for input such as type, description, or title
230
230
  # @option input_definition [String] :title Human readable title for input
231
231
  # @option input_definition [String] :description Description for the input
232
- # @option input_definition [String] :type text | textarea
232
+ # @option input_definition [String] :type text | textarea | radio
233
233
  # @option input_definition [String] :default The default value for the input
234
234
  # @option input_definition [Boolean] :optional Set to true to not require input for test execution
235
+ # @option input_definition [Hash] :options Possible input option formats based on input type
236
+ # @option options [Array] :list_options Array of options for input formats that require a list of possible values
235
237
  # @return [void]
236
238
  # @example
237
- # input :patientid, title: 'Patient ID', description: 'The ID of the patient being searched for',
239
+ # input :patient_id, title: 'Patient ID', description: 'The ID of the patient being searched for',
238
240
  # default: 'default_patient_id'
239
241
  # @example
240
242
  # input :textarea, title: 'Textarea Input Example', type: 'textarea', optional: true
@@ -252,14 +254,24 @@ module Inferno
252
254
 
253
255
  # Define outputs
254
256
  #
255
- # @param output_list [Symbol]
257
+ # @param identifier [Symbol] identifier for the output
258
+ # @param other_identifiers [Symbol] array of symbols if specifying multiple outputs
259
+ # @param output_definition [Hash] options for output
260
+ # @option output_definition [String] :type text | textarea | oauth_credentials
256
261
  # @return [void]
257
262
  # @example
258
- # output :patient_id, :bearer_token
259
- def output(*output_list)
260
- output_list.each do |output_identifier|
261
- outputs << output_identifier
262
- config.add_output(output_identifier)
263
+ # output :patient_id, :condition_id, :observation_id
264
+ # @example
265
+ # output :oauth_credentials, type: 'oauth_credentials'
266
+ def output(identifier, *other_identifiers, **output_definition)
267
+ if other_identifiers.present?
268
+ [identifier, *other_identifiers].compact.each do |output_identifier|
269
+ outputs << output_identifier
270
+ config.add_output(output_identifier)
271
+ end
272
+ else
273
+ outputs << identifier
274
+ config.add_output(identifier, output_definition)
263
275
  end
264
276
  end
265
277
 
@@ -186,6 +186,12 @@ module Inferno
186
186
  .map { |header_name, value| Header.new(name: header_name.downcase, value: value, type: 'request') }
187
187
  response_headers = response[:headers]
188
188
  .map { |header_name, value| Header.new(name: header_name.downcase, value: value, type: 'response') }
189
+ request_body =
190
+ if request.dig(:headers, 'Content-Type')&.include?('application/x-www-form-urlencoded')
191
+ URI.encode_www_form(request[:payload])
192
+ else
193
+ request[:payload]
194
+ end
189
195
 
190
196
  new(
191
197
  verb: request[:method],
@@ -193,7 +199,7 @@ module Inferno
193
199
  direction: direction,
194
200
  name: name,
195
201
  status: response[:code].to_i,
196
- request_body: request[:payload],
202
+ request_body: request_body,
197
203
  response_body: response[:body],
198
204
  test_session_id: test_session_id,
199
205
  headers: request_headers + response_headers
@@ -62,5 +62,24 @@ module Inferno
62
62
  super('The chosen runnable must be run as part of a group')
63
63
  end
64
64
  end
65
+
66
+ class UnknownAttributeException < RuntimeError
67
+ def initialize(attributes, klass)
68
+ attributes_string = attributes.map { |attribute| "'#{attribute}'" }.join(', ')
69
+ super("Unknown attributes for #{klass.name}: #{attributes_string}")
70
+ end
71
+ end
72
+
73
+ class UnknownSessionDataType < RuntimeError
74
+ def initialize(output)
75
+ super("Unknown type '#{output[:type]}' for '#{output[:name]}'.")
76
+ end
77
+ end
78
+
79
+ class BadSessionDataType < RuntimeError
80
+ def initialize(name, expected_class, actual_class)
81
+ super("Expected '#{name}' to be a #{expected_class.name}, but found a #{actual_class.name}.")
82
+ end
83
+ end
65
84
  end
66
85
  end
@@ -0,0 +1,13 @@
1
+ module FHIR
2
+ class Client
3
+ attr_accessor :oauth_credentials
4
+
5
+ def need_to_refresh?
6
+ oauth_credentials&.need_to_refresh?
7
+ end
8
+
9
+ def able_to_refresh?
10
+ oauth_credentials&.able_to_refresh?
11
+ end
12
+ end
13
+ end