testscript_engine 0.0.0

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,332 @@
1
+ module MessageHandler
2
+ attr_accessor :debug_mode, :modify_report
3
+
4
+ def space
5
+ @space ||= ''
6
+ end
7
+
8
+ def unit_of_space
9
+ " "
10
+ end
11
+
12
+ def increase_space
13
+ space << unit_of_space
14
+ end
15
+
16
+ def decrease_space
17
+ @space.chomp!(unit_of_space)
18
+ end
19
+
20
+ def newline
21
+ puts
22
+ end
23
+
24
+ def print_out(message)
25
+ if message.start_with?("START")
26
+ newline
27
+ increase_space
28
+ elsif message.start_with?("FINISH")
29
+ else
30
+ print unit_of_space
31
+ end
32
+ print space
33
+ puts message
34
+
35
+ if message.start_with?("FINISH")
36
+ decrease_space
37
+ end
38
+ nil
39
+ end
40
+
41
+ def info(message_type, *options)
42
+ print_out messages(message_type, *options)
43
+ end
44
+
45
+ def cascade_skips(message_type, actions, *options)
46
+ print_out "#{outcome_symbol("SKIP")} #{messages(message_type, *options)}"
47
+ increase_space
48
+ counter = 0
49
+ while counter < actions.length
50
+ action = actions[counter]
51
+ if action.operation
52
+ id = "Operation: [#{(action.operation.id || action.operation.label || 'unlabeled')}]"
53
+ else
54
+ id = "Assert: [#{(action.assert.id || action.assert.label || 'unlabeled')}]" if action.assert
55
+ end
56
+ skip(:eval_assert_result, "#{id} skipped.")
57
+ counter += 1
58
+ end
59
+ decrease_space
60
+ end
61
+
62
+ def load_scripts
63
+ print_out messages(:begin_loading_scripts, testscript_path)
64
+ super
65
+ print_out messages(:finish_loading_scripts)
66
+ end
67
+
68
+ def make_runnables
69
+ print_out messages(:begin_creating_runnables)
70
+ increase_space
71
+ super
72
+ decrease_space
73
+ print_out messages(:finish_creating_runnables)
74
+ end
75
+
76
+ def run(*args)
77
+ print_out messages(:begin_runnable_execution, script.id)
78
+ result = super
79
+ puts
80
+ print_out messages(:finish_runnable_execution)
81
+ result
82
+ end
83
+
84
+ def preprocess
85
+ print_out messages(:begin_preprocess)
86
+ super
87
+ print_out messages(:finish_preprocess)
88
+ end
89
+
90
+ def setup
91
+ print_out messages(:begin_setup)
92
+ super
93
+ print_out messages(:finish_setup)
94
+ end
95
+
96
+ def test
97
+ print_out messages(:begin_test)
98
+ super
99
+ print_out messages(:finish_test)
100
+ end
101
+
102
+ def teardown
103
+ print_out messages(:begin_teardown)
104
+ super
105
+ print_out messages(:finish_teardown)
106
+ end
107
+
108
+ def postprocessing
109
+ end
110
+
111
+ def load_fixtures
112
+ increase_space
113
+ super
114
+ decrease_space
115
+ end
116
+
117
+ def pass(message_type, *options)
118
+ message = messages(message_type, *options)
119
+ super()
120
+ print_out "#{outcome_symbol("INFO")} #{message}"
121
+ end
122
+
123
+ def fail(message_type, *options)
124
+ message = messages(message_type, *options)
125
+ super(message) if modify_report
126
+ print_out "#{outcome_symbol("FAIL")} #{message}"
127
+ end
128
+
129
+ def skip(message_type, *options)
130
+ message = messages(message_type, *options)
131
+ super(message) if modify_report
132
+ print_out "#{outcome_symbol("SKIP")} #{message}"
133
+ end
134
+
135
+ def warning(message_type, *options)
136
+ message = messages(message_type, *options)
137
+ super(message) if modify_report
138
+ print_out "#{outcome_symbol("WARN")} #{message}"
139
+ end
140
+
141
+ def error(message_type, *options)
142
+ message = messages(message_type, *options)
143
+ super(message) if modify_report
144
+ print_out "#{outcome_symbol("ERROR")} #{message}"
145
+ end
146
+
147
+ def print_action_header(action_type)
148
+ return false if @previous_action_type == action_type
149
+
150
+ @previous_action_type = action_type
151
+ true
152
+ end
153
+
154
+ # < ---- TO REVIEW ---- >
155
+ def client(*args)
156
+ client = super
157
+ FHIR.logger.formatter = logger_formatter_with_spacing
158
+ client
159
+ end
160
+
161
+ def logger_formatters_with_spacing
162
+ @logger_formatters_with_spacing ||= {}
163
+ end
164
+
165
+ def logger_formatter_with_spacing
166
+ logger_formatters_with_spacing[space.length] || begin
167
+ new_logger_formatter = proc do |severity, datetime, progname, msg|
168
+ "#{space}#{unit_of_space}#{msg}\n"
169
+ end
170
+ logger_formatters_with_spacing[space.length] = new_logger_formatter
171
+ new_logger_formatter
172
+ end
173
+ end
174
+
175
+ def outcome_symbol(outcome)
176
+ symbol = begin
177
+ case outcome
178
+ when "UNKNOWN"
179
+ "?¿?"
180
+ when "FATAL"
181
+ [023042].pack("U*")
182
+ when "ERROR"
183
+ [10071].pack("U*")
184
+ when "WARN"
185
+ [023220].pack("U*")
186
+ when "INFO"
187
+ [10003].pack("U*")
188
+ when "DEBUG"
189
+ [0372415].pack("U*")
190
+ when "FAIL"
191
+ [10007].pack("U*")
192
+ when "SKIP"
193
+ "\u21BB".encode('utf-8')
194
+ end
195
+ end
196
+
197
+ "(#{symbol})"
198
+ end
199
+
200
+ def begin_symbol
201
+ [10551].pack("U*")
202
+ end
203
+
204
+ def finish_symbol
205
+ [024465].pack("U*")
206
+ end
207
+
208
+ def messages(message, *options)
209
+ message_text = case message
210
+ when :abort_test
211
+ "Due to an unsuccessful action in the [#{options[0]}] phase, remaining actions in this test will be skipped. Skipping the next #{options[1]} action(s)."
212
+ when :bad_script
213
+ "Given non-TestScript resource. Can not create runnable."
214
+ when :bad_serialized_script
215
+ "Can not deserialize resource into TestScript: [#{options[0]}]."
216
+ when :begin_initialize_client
217
+ start_message_format("INITIALIZE CLIENT")
218
+ when :begin_creating_runnables
219
+ start_message_format("MAKE RUNNABLE(S)")
220
+ when :created_runnable
221
+ "Created runnable from TestScript: [#{options[0]}]."
222
+ when :finish_creating_runnables
223
+ finish_message_format("MAKING RUNNABLE(S)")
224
+ when :finish_initialize_client
225
+ finish_message_format("INITIALIZING CLIENT")
226
+ when :invalid_script
227
+ "Can not load TestScript. Invalid resource: [#{options[0]}]."
228
+ when :invalid_script
229
+ "Given invalid TestScript resource. Can not create runnable."
230
+ when :loaded_script
231
+ "Loaded TestScript: [#{options[0]}]."
232
+ when :loaded_static_fixture
233
+ "Loaded static fixture [#{options[0]}]."
234
+ when :no_postprocess
235
+ "Nothing to postprocess."
236
+ when :no_preprocess
237
+ "Nothing to preprocess."
238
+ when :no_setup
239
+ "Nothing to setup."
240
+ when :no_teardown
241
+ "Nothing to teardown."
242
+ when :overwrite_existing_script
243
+ "Overwriting previously loaded TestScript: [#{options[0]}]."
244
+ when :skip_on_fail
245
+ "Due to the preceeding unsuccessful action, skipping the next #{options[0]} action(s)."
246
+ when :unable_to_create_runnable
247
+ "Can not create runnable from TestScript: [#{options[0]}]."
248
+ when :unable_to_locate_runnable
249
+ "Can not locate runnable with id: [#{options[0]}]. Can not execute."
250
+ when :assertion_error
251
+ "ERROR: Unable to process assertion: #{options[0]}"
252
+ when :assertion_exception
253
+ "#{options[0]}"
254
+ when :bad_reference
255
+ "Unable to read contents of reference: [#{options[0]}]. No reference extracted."
256
+ when :bad_request
257
+ "Unable to create a request from operation."
258
+ when :bad_static_fixture_reference
259
+ "Static fixture included unresolvable reference. Can not load fixture. Moving on."
260
+ when :begin_loading_scripts
261
+ start_message_format("LOAD TESTSCRIPTS", options[0])
262
+ when :begin_preprocess
263
+ start_message_format("PREPROCESS", options[0])
264
+ when :begin_runnable_execution
265
+ start_message_format("EXECUTE RUNNABLE", options[0])
266
+ when :begin_setup
267
+ start_message_format("SETUP")
268
+ when :begin_teardown
269
+ start_message_format("TEARDOWN")
270
+ when :begin_test
271
+ start_message_format("TEST")
272
+ when :eval_assert_result
273
+ "#{options[0]}"
274
+ when :evaluate_assert
275
+ "EVALUATING ASSERTION"
276
+ when :execute_operation
277
+ "OPERATION EXECUTION"
278
+ when :execute_operation_error
279
+ "Unable to execute operation. ERROR: [#{options[0]}]. [#{options[1]}]"
280
+ when :finish_loading_scripts
281
+ finish_message_format("LOADING SCRIPTS")
282
+ when :finish_preprocess
283
+ finish_message_format("PREPROCESS")
284
+ when :finish_runnable_execution
285
+ finish_message_format("EXECUTING RUNNABLE. FINAL EXECUTION SCORE: [#{testreport.score}]")
286
+ when :finish_setup
287
+ finish_message_format("SETUP")
288
+ when :finish_teardown
289
+ finish_message_format("TEARDOWN")
290
+ when :finish_test
291
+ finish_message_format("TEST")
292
+ when :invalid_assert
293
+ "Invalid assert. Can not evaluate."
294
+ when :invalid_dump
295
+ "Validation error: [#{options[0]}]"
296
+ when :invalid_operation
297
+ "Invalid operation. Can not execute."
298
+ when :invalid_request
299
+ "Unable to create a request using operation: [#{options[0]}]. Can not execute."
300
+ when :no_contained_resource
301
+ "Reference [#{options[0]}] refers to a contained resource that does not exist. Moving on."
302
+ when :no_path
303
+ "Unable to extract path from operation."
304
+ when :no_reference
305
+ "Reference element of reference object is nil. Can not get resource from reference."
306
+ when :no_static_fixture_id
307
+ "No ID for static fixture. Can not load."
308
+ when :no_static_fixture_reference
309
+ "No reference for static fixture. Can not load."
310
+ when :no_static_fixture_resource
311
+ "No resource for static fixture. Can not load."
312
+ when :pass_execute_operation
313
+ "Executed Operation: [#{options[0]}]"
314
+ when :resource_extraction
315
+ "Unable to extract resource referenced by [#{options[0]}]. Encountered: [#{options[1]}]."
316
+ when :uncaught_error
317
+ "Uncaught error: [#{options[0]}]."
318
+ when :unsupported_ref
319
+ "Remote reference: [#{options[0]}] not supported. No reference extracted."
320
+ else
321
+ "! unknown message type !"
322
+ end
323
+ end
324
+ end
325
+
326
+ def start_message_format(phase, *options)
327
+ "STARTING TO #{phase}" + (options[0] ? ": [#{options[0]}]" : '')
328
+ end
329
+
330
+ def finish_message_format(phase)
331
+ "FINISHED #{phase}."
332
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+ module Operation
3
+ class OperationException < StandardError
4
+ attr_reader :details
5
+
6
+ def initialize(details)
7
+ @details = details
8
+ super()
9
+ end
10
+ end
11
+
12
+ OPERATION_NEEDS_RESOURCE_TYPE = %w[
13
+ put
14
+ patch
15
+ delete
16
+ read
17
+ vread
18
+ update
19
+ create
20
+ ].freeze
21
+
22
+ INTERACTION_NEEDS_PAYLOAD = %w[
23
+ patch
24
+ post
25
+ put
26
+ ].freeze
27
+
28
+ def execute(operation)
29
+ request = build_request(operation)
30
+
31
+ begin
32
+ client.send(*request)
33
+ rescue
34
+ raise OperationException, :bad_request
35
+ end
36
+
37
+ storage(operation)
38
+ pass(:pass_execute_operation, operation.label || 'unlabeled')
39
+ end
40
+
41
+ def build_request(operation)
42
+ path = get_path(operation)
43
+ headers = get_headers(operation)
44
+ interaction = get_interaction(operation)
45
+ payload = get_payload(operation, interaction)
46
+
47
+ [interaction, path, payload, headers].compact
48
+ end
49
+
50
+ def get_path(operation)
51
+ return replace_variables(operation.url) if operation.url
52
+
53
+ if operation.params
54
+ # [type]/[params]
55
+ "#{get_resource_type(operation)}#{replace_variables(operation.params)}"
56
+ elsif operation.targetId
57
+ resource = get_resource(operation.targetId)
58
+ # TODO: Move this exception into the get_resource method in runnable/utilities
59
+ # just need to make sure that raising this exception is always going to be acceptbale i.e. in any action ever,
60
+ # if the resource isn't available, we should dip
61
+ (raise OperationException, :noResource) unless resource
62
+ path = resource.resourceType
63
+ id = get_id(operation, resource)
64
+ vid = get_vid(operation, resource)
65
+ "#{path}#{id}" + (operation.type&.code == 'history' ? '/_history' : vid)
66
+ elsif operation.sourceId
67
+ resource = get_resource(operation.sourceId)
68
+ raise OperationException, :noResource unless resource
69
+
70
+ %w[batch transaction].include?(operation.type&.code) ? '' : resource.resourceType
71
+ else
72
+ raise OperationException, :no_path
73
+ end
74
+ end
75
+
76
+ def get_headers(operation)
77
+ headers = {
78
+ 'Content-Type' => get_format(operation.contentType),
79
+ 'Accept' => get_format(operation.accept)
80
+ }
81
+
82
+ operation.requestHeader.each_with_object(headers) do |header|
83
+ headers[header.field] = header.value
84
+ end
85
+ end
86
+
87
+ def get_interaction(operation)
88
+ interaction = operation.local_method || operation_to_interaction(operation.type&.code)
89
+ interaction || (raise OperationException, :noInteraction)
90
+ end
91
+
92
+ def get_payload(operation, interaction)
93
+ return unless INTERACTION_NEEDS_PAYLOAD.include?(interaction)
94
+
95
+ payload = get_resource(operation.sourceId)
96
+ raise OperationException, :noPayload if INTERACTION_NEEDS_PAYLOAD.include?(interaction) && !payload
97
+ payload
98
+ end
99
+
100
+ def get_resource_type(operation)
101
+ operation_type = operation.local_method || operation.type.code
102
+
103
+ if OPERATION_NEEDS_RESOURCE_TYPE.include?(operation_type) && operation.resource.nil?
104
+ raise OperationException, :noResourceType
105
+ end
106
+
107
+ operation.resource
108
+ end
109
+
110
+ def get_id(operation, resource)
111
+ id = resource.id
112
+ (raise OperationException, :noId) ['read', 'vread'].include?(operation.type&.code) if id.nil?
113
+ "/#{id}"
114
+ end
115
+
116
+ def get_vid(operation, resource)
117
+ vid = resource.meta&.versionId
118
+ if operation.type&.code == 'vread'
119
+ (raise OperationException, :noVid) unless vid
120
+ "/_history/#{vid}"
121
+ else
122
+ ''
123
+ end
124
+ end
125
+
126
+ def get_format(format)
127
+ return 'application/fhir+xml' if format == 'xml'
128
+
129
+ 'application/fhir+json'
130
+ end
131
+
132
+ def operation_to_interaction(interaction)
133
+ case interaction
134
+ when 'read', 'vread', 'search', 'search-type', 'search-system', 'capabilities', 'history', 'history-instance', 'history-type', 'history-system', 'operation'
135
+ 'get'
136
+ when 'create', 'batch', 'transaction'
137
+ 'post'
138
+ when 'update'
139
+ 'put'
140
+ when 'patch'
141
+ 'patch'
142
+ when 'delete'
143
+ 'delete'
144
+ end
145
+ end
146
+
147
+ def create_operation(source_id)
148
+ FHIR::TestScript::Setup::Action::Operation.new({
149
+ sourceId: source_id,
150
+ method: 'post'
151
+ })
152
+ end
153
+
154
+ def delete_operation(source_id)
155
+ FHIR::TestScript::Setup::Action::Operation.new({
156
+ targetId: id_map[source_id],
157
+ method: 'delete'
158
+ })
159
+ end
160
+ end