testscript_engine 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/testscript_engine +108 -0
- data/lib/testscript_engine/assertion.rb +284 -0
- data/lib/testscript_engine/message_handler.rb +332 -0
- data/lib/testscript_engine/operation.rb +160 -0
- data/lib/testscript_engine/testreport_handler.rb +247 -0
- data/lib/testscript_engine/testscript_runnable.rb +252 -0
- data/lib/testscript_engine.rb +134 -0
- metadata +140 -0
@@ -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
|