Class: ApiMiniTester::TestStep
- Inherits:
-
Object
- Object
- ApiMiniTester::TestStep
- Includes:
- HTTParty
- Defined in:
- lib/api_mini_tester/test_step.rb
Constant Summary collapse
- SUPPORTED_METHODS =
%i[ get post put delete ].freeze
- SUPPORTED_RANDOM_DISTRIBUTION =
%w[ static norm uniform ].freeze
- TYPE_TO_METHOD_MAP =
{ 'integer' => 'to_i', 'float' => 'to_f', 'string' => 'to_s' }.freeze
Instance Attribute Summary collapse
-
#debug ⇒ Object
readonly
Returns the value of attribute debug.
-
#input ⇒ Object
Returns the value of attribute input.
-
#method ⇒ Object
readonly
Returns the value of attribute method.
-
#name ⇒ Object
Returns the value of attribute name.
-
#output ⇒ Object
Returns the value of attribute output.
-
#results ⇒ Object
readonly
Returns the value of attribute results.
-
#sleeps ⇒ Object
readonly
Returns the value of attribute sleeps.
-
#timing ⇒ Object
Returns the value of attribute timing.
-
#uri ⇒ Object
readonly
Returns the value of attribute uri.
Instance Method Summary collapse
- #add_result(section, result) ⇒ Object
- #array_diff(a, b, path = nil, section = :body) ⇒ Object
- #assert_body(response, output) ⇒ Object
- #assert_headers(response, output) ⇒ Object
- #assert_status(response, output) ⇒ Object
- #assert_timing(runtime, limit = nil) ⇒ Object
- #body ⇒ Object
- #body_by_anotations ⇒ Object
- #body_to_form_data ⇒ Object
- #body_to_urlencoded ⇒ Object
- #content_type ⇒ Object
- #hash_diff(a, b, path = nil, section = :body) ⇒ Object
- #headers ⇒ Object
-
#initialize(base_uri, step, context = nil, data = nil, defaults = nil, debug = false) ⇒ TestStep
constructor
A new instance of TestStep.
- #log_debug(request, response, expectations) ⇒ Object
- #print_results ⇒ Object
- #raw_body ⇒ Object
- #resolve_annotations(struct) ⇒ Object
- #resolve_annotations_array(array, type) ⇒ Object
- #resolve_annotations_hash(hash) ⇒ Object
- #resolve_annotations_value(value, type) ⇒ Object
- #run_asserts(response) ⇒ Object
- #run_step ⇒ Object
- #step_sleep(params) ⇒ Object
- #test_body ⇒ Object
- #test_headers ⇒ Object
- #test_status ⇒ Object
- #test_timing ⇒ Object
- #type_to_method(type) ⇒ Object
- #valid? ⇒ Boolean
Constructor Details
#initialize(base_uri, step, context = nil, data = nil, defaults = nil, debug = false) ⇒ TestStep
Returns a new instance of TestStep.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/api_mini_tester/test_step.rb', line 25 def initialize(base_uri, step, context = nil, data = nil, defaults = nil, debug = false) @debug = debug if debug @debug_output = File.open(ApiMiniTester::TestSuite::DEBUG_FILE, 'a') end step = step.deep_merge!(defaults) do |key, this_val, other_val| if this_val.nil? other_val elsif this_val.is_a?(Array) (this_val + other_val) else this_val end end if defaults Liquid::Template.register_filter(::TestFakerFilter) @context = context uri_template = Liquid::Template.parse([base_uri, step['uri']].join("/"), error_mode: :strict) @name = step['name'] @sleeps = step['sleep'] @uri = uri_template.render( {'context' => context, 'data' => data}, { strict_variables: true }) @method = step['method'].downcase.to_sym input_template = Liquid::Template.parse(step['input'].to_yaml.to_s, error_mode: :strict) @input = YAML.load( input_template.render({'context' => context, 'data' => data}, { strict_variables: true })) output_template = Liquid::Template.parse(step['output'].to_yaml.to_s, error_mode: :strict) @output = YAML.load( output_template.render({'context' => context, 'data' => data}, { strict_variables: true })) @results = { name: step['name'], desc: step['desc'], status: [], headers: [], body: [], url: [], method: [], timing: [] } end |
Instance Attribute Details
#debug ⇒ Object (readonly)
Returns the value of attribute debug.
23 24 25 |
# File 'lib/api_mini_tester/test_step.rb', line 23 def debug @debug end |
#input ⇒ Object
Returns the value of attribute input.
22 23 24 |
# File 'lib/api_mini_tester/test_step.rb', line 22 def input @input end |
#method ⇒ Object (readonly)
Returns the value of attribute method.
23 24 25 |
# File 'lib/api_mini_tester/test_step.rb', line 23 def method @method end |
#name ⇒ Object
Returns the value of attribute name.
22 23 24 |
# File 'lib/api_mini_tester/test_step.rb', line 22 def name @name end |
#output ⇒ Object
Returns the value of attribute output.
22 23 24 |
# File 'lib/api_mini_tester/test_step.rb', line 22 def output @output end |
#results ⇒ Object (readonly)
Returns the value of attribute results.
23 24 25 |
# File 'lib/api_mini_tester/test_step.rb', line 23 def results @results end |
#sleeps ⇒ Object (readonly)
Returns the value of attribute sleeps.
23 24 25 |
# File 'lib/api_mini_tester/test_step.rb', line 23 def sleeps @sleeps end |
#timing ⇒ Object
Returns the value of attribute timing.
22 23 24 |
# File 'lib/api_mini_tester/test_step.rb', line 22 def timing @timing end |
#uri ⇒ Object (readonly)
Returns the value of attribute uri.
23 24 25 |
# File 'lib/api_mini_tester/test_step.rb', line 23 def uri @uri end |
Instance Method Details
#add_result(section, result) ⇒ Object
192 193 194 |
# File 'lib/api_mini_tester/test_step.rb', line 192 def add_result(section, result) @results[section] << result end |
#array_diff(a, b, path = nil, section = :body) ⇒ Object
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/api_mini_tester/test_step.rb', line 246 def array_diff(a, b, path = nil, section = :body) a.each do |a_item| if b.nil? add_result section, { result: false, name: "Response boby value: #{[path].join(".")}", desc: "Assert #{[path].join(".")} is empty" } elsif a_item.instance_of?(Hash) found = false b.each do |b_item| matching = true a_item.each_key do |k, v| matching = (b_item[k] == a_item[k]) if matching end found = true if matching end add_result section, { result: found, name: "Response body value: #{[path].join(".")}", desc: "Assert #{[path].join(".")} #{found ? 'contains' : 'does not contains'} #{a_item}" } elsif a_item.instance_of?(Array) # TODO: Add support for array of array it isn't so needed to compate so deep structures else add_result section, { result: b.include?(a_item), name: "Response boby value: #{[path].join(".")}", desc: "Assert #{[path].join(".")} #{b.include?(a_item) ? 'contains' : 'does not contains'} #{a_item}" } end end end |
#assert_body(response, output) ⇒ Object
238 239 240 241 242 243 244 |
# File 'lib/api_mini_tester/test_step.rb', line 238 def assert_body(response, output) if output.instance_of?(Hash) hash_diff(output, response) elsif output.instance_of?(Array) array_diff(output, response) end end |
#assert_headers(response, output) ⇒ Object
227 228 229 230 231 232 233 234 235 236 |
# File 'lib/api_mini_tester/test_step.rb', line 227 def assert_headers(response, output) return if output.nil? output.each do |k, v| add_result :headers, { result: (v == response[k]), name: "Header value: #{k} == #{v}", desc: "Header #{k} expected: #{v}, got #{response[k]}", exp: v, real: response[k] } end end |
#assert_status(response, output) ⇒ Object
220 221 222 223 224 225 |
# File 'lib/api_mini_tester/test_step.rb', line 220 def assert_status(response, output) add_result :status, { result: (response == output), name: "Response code == #{output}", desc: "Expected response #{output}, got response #{response}", exp: output, real: response } end |
#assert_timing(runtime, limit = nil) ⇒ Object
212 213 214 215 216 217 218 |
# File 'lib/api_mini_tester/test_step.rb', line 212 def assert_timing(runtime, limit = nil) limit ||= Float::INFINITY add_result :timing, { result: (runtime < limit), name: "Request time < #{limit}", desc: "Expected request time #{limit}, real time #{runtime}", exp: limit, real: runtime } end |
#body ⇒ Object
82 83 84 85 86 87 88 89 90 91 |
# File 'lib/api_mini_tester/test_step.rb', line 82 def body case content_type when 'application/x-www-form-urlencoded' body_to_urlencoded when 'multipart/form-data' body_to_form_data else body_by_anotations end end |
#body_by_anotations ⇒ Object
93 94 95 |
# File 'lib/api_mini_tester/test_step.rb', line 93 def body_by_anotations resolve_annotations(@input["body"]).to_json end |
#body_to_form_data ⇒ Object
103 104 105 106 107 108 109 110 |
# File 'lib/api_mini_tester/test_step.rb', line 103 def body_to_form_data body = {} @input["body"].each do |item| body[item['name']] = item['value'] if item['type'] == 'input' body[item['name']] = File.open(item['value'], 'r') if item['type'] == 'file' end body end |
#body_to_urlencoded ⇒ Object
112 113 114 115 116 117 118 |
# File 'lib/api_mini_tester/test_step.rb', line 112 def body_to_urlencoded body = [] @input["body"].each do |key, value| body << [key, value] end URI.encode_www_form(body) end |
#content_type ⇒ Object
72 73 74 |
# File 'lib/api_mini_tester/test_step.rb', line 72 def content_type @input['content_type'] || 'application/json' end |
#hash_diff(a, b, path = nil, section = :body) ⇒ Object
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/api_mini_tester/test_step.rb', line 274 def hash_diff(a, b, path = nil, section = :body) return nil if a.nil? || b.nil? a.each_key do |k, v| current_path = [path, k].join('.') if b[k].nil? add_result section, { result: false, name: "Reponse value: #{[path, k].join(".")}", desc: "Missing #{current_path}" } elsif v.instance_of?(Hash) hash_diff(a[k], b[k], current_path, section) elsif v.instance_of?(Array) array_diff(a[k], b[k], current_path, section) else add_result section, { result: (a[k] == b[k]), name: "Reponse body value: #{[path, k].join(".")}", desc: "Assert #{[path, k].join(".")}: #{a[k]} #{a[k] == b[k] ? '==' : '!='} #{b[k]}" } end end end |
#headers ⇒ Object
76 77 78 79 80 |
# File 'lib/api_mini_tester/test_step.rb', line 76 def headers @input['header'] = {} unless @input['header'] @input['header']['Content-Type'] ||= content_type @input['header'] end |
#log_debug(request, response, expectations) ⇒ Object
181 182 183 184 |
# File 'lib/api_mini_tester/test_step.rb', line 181 def log_debug(request, response, expectations) log = { uri: uri, method: method, request: request, response: response, expectations: expectations } @debug_output.puts log.to_json end |
#print_results ⇒ Object
186 187 188 189 190 |
# File 'lib/api_mini_tester/test_step.rb', line 186 def print_results @results.each do |line| puts line end end |
#raw_body ⇒ Object
97 98 99 100 101 |
# File 'lib/api_mini_tester/test_step.rb', line 97 def raw_body @input["body"].to_hash rescue StandardError "" end |
#resolve_annotations(struct) ⇒ Object
299 300 301 302 303 304 |
# File 'lib/api_mini_tester/test_step.rb', line 299 def resolve_annotations(struct) if struct.instance_of?(Hash) struct = resolve_annotations_hash(struct) end struct end |
#resolve_annotations_array(array, type) ⇒ Object
327 328 329 |
# File 'lib/api_mini_tester/test_step.rb', line 327 def resolve_annotations_array(array, type) array.map(&type_to_method(type)) end |
#resolve_annotations_hash(hash) ⇒ Object
306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/api_mini_tester/test_step.rb', line 306 def resolve_annotations_hash(hash) hash.each do |k, v| next unless matched = /\A\.(?<key>.*)\Z/.match(k) type = v typed_key = matched[:key] next if hash[typed_key].nil? hash[typed_key] = if hash[typed_key].instance_of?(Array) resolve_annotations_array(hash[typed_key], type) else resolve_annotations_value(hash[typed_key], type) end hash.delete(k) end end |
#resolve_annotations_value(value, type) ⇒ Object
321 322 323 324 325 |
# File 'lib/api_mini_tester/test_step.rb', line 321 def resolve_annotations_value(value, type) value.send(type_to_method(type)) rescue StandardError value end |
#run_asserts(response) ⇒ Object
174 175 176 177 178 179 |
# File 'lib/api_mini_tester/test_step.rb', line 174 def run_asserts(response) assert_status(response.code, test_status) assert_headers(response.headers, test_headers) assert_body(response.parsed_response, test_body) assert_timing(timing, test_timing) end |
#run_step ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/api_mini_tester/test_step.rb', line 136 def run_step if sleeps step_sleep(sleeps['before']) if sleeps['before'] end @timing = Time.now case method when :get response = HTTParty.get(uri, headers: headers) when :post response = HTTParty.post(uri, headers: headers, body: body) when :put response = HTTParty.put(uri, headers: headers, body: body) when :delete response = HTTParty.delete(uri, headers: headers) when :patch response = HTTParty.patch(uri, headers: headers, body: body) else raise "Unknown HTTP method: #{method}" end @timing = Time.now - @timing if sleeps step_sleep(sleeps['after']) if sleeps['after'] end add_result :url, { result: true, desc: "Url: #{uri}" } add_result :method, { result: true, desc: "Method: #{method}" } log_debug({ headers: headers, body: raw_body }, { headers: response.headers, body: response.parsed_response, code: response.code }, output) if debug run_asserts(response) [ results, response ] end |
#step_sleep(params) ⇒ Object
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/api_mini_tester/test_step.rb', line 196 def step_sleep(params) params['distribution'] = 'static' unless SUPPORTED_RANDOM_DISTRIBUTION.include?(params['distribution']) t = begin case params['distribution'] when 'static' params['value'] when 'norm' Distribution::Normal.rng(params['mean'], params['sigma'], srand).call when 'uniform' Random.new.rand(params['max'] - params['min']) + params['min'] end end sleep(t.to_f.abs) if t end |
#test_body ⇒ Object
124 125 126 |
# File 'lib/api_mini_tester/test_step.rb', line 124 def test_body @output['body'] end |
#test_headers ⇒ Object
120 121 122 |
# File 'lib/api_mini_tester/test_step.rb', line 120 def test_headers @output['header'] end |
#test_status ⇒ Object
128 129 130 |
# File 'lib/api_mini_tester/test_step.rb', line 128 def test_status @output['status'] end |
#test_timing ⇒ Object
132 133 134 |
# File 'lib/api_mini_tester/test_step.rb', line 132 def test_timing @output['timing'] end |
#type_to_method(type) ⇒ Object
295 296 297 |
# File 'lib/api_mini_tester/test_step.rb', line 295 def type_to_method(type) TYPE_TO_METHOD_MAP[type.downcase].to_sym end |
#valid? ⇒ Boolean
62 63 64 65 66 67 68 69 70 |
# File 'lib/api_mini_tester/test_step.rb', line 62 def valid? return false if uri.nil? || uri.empty? return false unless URI.parse(uri) rescue false return false unless SUPPORTED_METHODS.include? method return false if @name.nil? || @name.empty? true end |