testscript_engine 0.0.0

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