api-tester 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependabot.yml +29 -0
  3. data/.github/workflows/push.yml +1 -1
  4. data/.github/workflows/test.yml +3 -3
  5. data/.rubocop.yml +136 -26
  6. data/README.md +1 -1
  7. data/api-tester.gemspec +3 -3
  8. data/lib/api-tester/config.rb +1 -2
  9. data/lib/api-tester/definition/boundary_case.rb +1 -3
  10. data/lib/api-tester/definition/contract.rb +4 -4
  11. data/lib/api-tester/definition/endpoint.rb +35 -17
  12. data/lib/api-tester/definition/fields/array_field.rb +8 -4
  13. data/lib/api-tester/definition/fields/boolean_field.rb +9 -2
  14. data/lib/api-tester/definition/fields/email_field.rb +16 -2
  15. data/lib/api-tester/definition/fields/enum_field.rb +8 -4
  16. data/lib/api-tester/definition/fields/field.rb +12 -6
  17. data/lib/api-tester/definition/fields/number_field.rb +13 -2
  18. data/lib/api-tester/definition/fields/object_field.rb +8 -4
  19. data/lib/api-tester/definition/fields/plain_array_field.rb +2 -2
  20. data/lib/api-tester/definition/method.rb +1 -3
  21. data/lib/api-tester/definition/request.rb +7 -10
  22. data/lib/api-tester/definition/response.rb +11 -4
  23. data/lib/api-tester/method_case_test.rb +8 -8
  24. data/lib/api-tester/modules/benchmark_module.rb +35 -0
  25. data/lib/api-tester/modules/extra_verbs.rb +7 -1
  26. data/lib/api-tester/modules/good_case.rb +1 -1
  27. data/lib/api-tester/modules/good_variations.rb +69 -0
  28. data/lib/api-tester/modules/injection_module.rb +12 -6
  29. data/lib/api-tester/modules/missing_resource.rb +64 -0
  30. data/lib/api-tester/modules/server_information.rb +1 -2
  31. data/lib/api-tester/modules/typo.rb +3 -1
  32. data/lib/api-tester/modules/unused_fields.rb +1 -1
  33. data/lib/api-tester/reporter/api_report.rb +3 -2
  34. data/lib/api-tester/reporter/missing_field_report.rb +1 -4
  35. data/lib/api-tester/reporter/report.rb +2 -6
  36. data/lib/api-tester/reporter/response_time_report.rb +24 -0
  37. data/lib/api-tester/reporter/status_code_report.rb +1 -2
  38. data/lib/api-tester/test_helper.rb +2 -0
  39. data/lib/api-tester/util/response_evaluator.rb +37 -22
  40. data/lib/api-tester/util/supported_verbs.rb +2 -2
  41. data/lib/api-tester/version.rb +1 -1
  42. metadata +14 -9
@@ -5,10 +5,7 @@ require 'api-tester/definition/boundary_case'
5
5
  module ApiTester
6
6
  # Class for defining requests in a contract
7
7
  class Request
8
- attr_accessor :definition
9
- attr_accessor :header_fields
10
- attr_accessor :fields
11
- attr_accessor :query_params
8
+ attr_accessor :definition, :header_fields, :fields, :query_params
12
9
 
13
10
  def initialize
14
11
  self.fields = []
@@ -27,7 +24,7 @@ module ApiTester
27
24
  end
28
25
 
29
26
  def default_query
30
- query_params.map { |param| "#{param.name}=#{param.default_value}" }.join('&')
27
+ query_params.map { |param| "#{param.name}=#{param.default}" }.join('&')
31
28
  end
32
29
 
33
30
  def add_header_field(new_header)
@@ -39,7 +36,7 @@ module ApiTester
39
36
  response = {}
40
37
  fields.each do |field|
41
38
  if field.required == true
42
- response[field.name] = field.default_value
39
+ response[field.name] = field.default
43
40
  end
44
41
  end
45
42
  response
@@ -50,17 +47,17 @@ module ApiTester
50
47
  end
51
48
 
52
49
  def default_headers
53
- if header_fields != []
54
- headers
55
- else
50
+ if header_fields == []
56
51
  { content_type: :json, accept: :json }
52
+ else
53
+ headers
57
54
  end
58
55
  end
59
56
 
60
57
  def headers
61
58
  header_response = {}
62
59
  header_fields.each do |header_field|
63
- header_response[header_field.name] = header_field.default_value
60
+ header_response[header_field.name] = header_field.default
64
61
  end
65
62
  header_response
66
63
  end
@@ -3,8 +3,7 @@
3
3
  module ApiTester
4
4
  # Class for defining expected responses
5
5
  class Response
6
- attr_accessor :code
7
- attr_accessor :body
6
+ attr_accessor :code, :body
8
7
 
9
8
  def initialize(status_code: 200)
10
9
  self.code = status_code
@@ -19,7 +18,11 @@ module ApiTester
19
18
  def to_s
20
19
  des = {}
21
20
  body.map do |f|
22
- des[f.name] = field_display f
21
+ if f.has_key
22
+ des[f.name] = field_display f
23
+ else
24
+ des = field_display f
25
+ end
23
26
  end
24
27
  des.to_json
25
28
  end
@@ -29,7 +32,11 @@ module ApiTester
29
32
  if field.subfields?
30
33
  des = {}
31
34
  field.fields.map do |f|
32
- des[f.name] = field_display f
35
+ if f.has_key
36
+ des[f.name] = field_display f
37
+ else
38
+ des = field_display f
39
+ end
33
40
  end
34
41
  des.to_json
35
42
  end
@@ -1,16 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'api-tester/util/response_evaluator.rb'
3
+ require 'api-tester/util/response_evaluator'
4
4
 
5
5
  module ApiTester
6
6
  # Class for testing methods
7
7
  class MethodCaseTest
8
- attr_accessor :expected_response
9
- attr_accessor :payload
10
- attr_accessor :response
11
- attr_accessor :reports
12
- attr_accessor :url
13
- attr_accessor :module_name
8
+ attr_accessor :expected_response, :payload, :response, :reports, :url, :module_name
14
9
 
15
10
  def initialize(response:, payload:, expected_response:, url:, verb:, module_name:)
16
11
  self.payload = payload
@@ -22,6 +17,7 @@ module ApiTester
22
17
  end
23
18
 
24
19
  def response_code_report
20
+ print 'F'
25
21
  report = StatusCodeReport.new description: "#{module_name} - Incorrect response code",
26
22
  url: url,
27
23
  request: payload,
@@ -32,6 +28,7 @@ module ApiTester
32
28
  end
33
29
 
34
30
  def missing_field_report(field)
31
+ print 'F'
35
32
  report = Report.new description: "#{module_name} - Missing field #{field}",
36
33
  url: url,
37
34
  request: payload,
@@ -42,6 +39,7 @@ module ApiTester
42
39
  end
43
40
 
44
41
  def extra_field_report(field)
42
+ print 'F'
45
43
  report = Report.new description: "#{module_name} - Found extra field #{field}",
46
44
  url: url,
47
45
  request: payload,
@@ -53,6 +51,7 @@ module ApiTester
53
51
 
54
52
  def check
55
53
  if check_response_code
54
+ print '.'
56
55
  evaluator = ApiTester::ResponseEvaluator.new actual_body: json_parse(response.body),
57
56
  expected_fields: expected_response
58
57
  evaluator.missing_fields.map { |field| missing_field_report(field) }
@@ -63,7 +62,8 @@ module ApiTester
63
62
  end
64
63
 
65
64
  def check_response_code
66
- if response.code != expected_response.code
65
+ if response && (response.code != expected_response.code)
66
+ print 'F'
67
67
  response_code_report
68
68
  return false
69
69
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/reporter/response_time_report'
4
+
5
+ module ApiTester
6
+ # Checks the response times collected during the test run
7
+ # Note: Needs at least one calling module, like GoodCase, to work
8
+ module BenchmarkModule
9
+ def self.go(contract)
10
+ reports = []
11
+
12
+ contract.endpoints.each do |endpoint|
13
+ longest_time = endpoint.longest_time
14
+ longest_time[:time] = longest_time[:time] * 1000.0 # Convert from seconds to ms
15
+ if longest_time[:time] > contract.max_time
16
+ print 'F'
17
+ reports << ResponseTimeReport.new(url: endpoint.url,
18
+ verb: longest_time[:verb],
19
+ payload: longest_time[:payload],
20
+ max_time: contract.max_time,
21
+ actual_time: longest_time[:time],
22
+ description: 'BenchmarkModule')
23
+ else
24
+ print '.'
25
+ end
26
+ end
27
+
28
+ reports
29
+ end
30
+
31
+ def self.order
32
+ 99
33
+ end
34
+ end
35
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'api-tester/util/supported_verbs'
4
+ require 'api-tester/definition/method'
5
+ require 'api-tester/method_case_test'
4
6
 
5
7
  module ApiTester
6
8
  # Check verbs not explicitly defined in contract
@@ -9,7 +11,7 @@ module ApiTester
9
11
  reports = []
10
12
 
11
13
  contract.endpoints.each do |endpoint|
12
- extras = ApiTester::SupportedVerbs.all - endpoint.verbs
14
+ extras = supported_verbs - endpoint.verbs
13
15
  headers = endpoint.methods[0].request.default_headers
14
16
  extras.each do |verb|
15
17
  verb_case = BoundaryCase.new description: "Verb check with #{verb} for #{endpoint.name}",
@@ -34,6 +36,10 @@ module ApiTester
34
36
  reports
35
37
  end
36
38
 
39
+ def self.supported_verbs
40
+ ApiTester::SupportedVerbs.all
41
+ end
42
+
37
43
  def self.order
38
44
  3
39
45
  end
@@ -11,7 +11,7 @@ module ApiTester
11
11
 
12
12
  contract.endpoints.each do |endpoint|
13
13
  endpoint.methods.each do |method|
14
- default_case = BoundaryCase.new description: contract.base_url + endpoint.url,
14
+ default_case = BoundaryCase.new description: contract.base_url + endpoint.display_url,
15
15
  payload: method.request.default_payload,
16
16
  headers: method.request.default_headers
17
17
  response = endpoint.call base_url: contract.base_url,
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/reporter/status_code_report'
4
+ require 'api-tester/method_case_test'
5
+
6
+ module ApiTester
7
+ # Checks the good case as defined in contract
8
+ module GoodVariations
9
+ def self.go(contract)
10
+ reports = []
11
+
12
+ contract.endpoints.each do |endpoint|
13
+ endpoint.methods.each do |method|
14
+ method.request.fields.each do |field|
15
+ field.good_cases.each do |value|
16
+ payload = method.request.default_payload
17
+ payload[field.name] = value
18
+ call = BoundaryCase.new description: contract.base_url + endpoint.display_url,
19
+ payload: payload,
20
+ headers: method.request.default_headers
21
+ response = endpoint.call base_url: contract.base_url,
22
+ method: method,
23
+ payload: payload,
24
+ headers: call.headers
25
+ test = GoodVariationTest.new response: response,
26
+ url: contract.base_url + endpoint.url,
27
+ method: method
28
+ reports.concat test.check
29
+ end
30
+ end
31
+ method.request.query_params.each do |field|
32
+ field.good_cases.each do |value|
33
+ payload = method.request.default_payload
34
+ payload[field.name] = value
35
+ call = BoundaryCase.new description: contract.base_url + endpoint.display_url,
36
+ payload: payload,
37
+ headers: method.request.default_headers
38
+ response = endpoint.call base_url: contract.base_url,
39
+ method: method,
40
+ payload: payload,
41
+ headers: call.headers
42
+ test = GoodVariationTest.new response: response,
43
+ url: contract.base_url + endpoint.url,
44
+ method: method
45
+ reports.concat test.check
46
+ end
47
+ end
48
+ end
49
+ end
50
+ reports
51
+ end
52
+
53
+ def self.order
54
+ 1
55
+ end
56
+ end
57
+
58
+ # Test layout used by module
59
+ class GoodVariationTest < MethodCaseTest
60
+ def initialize(response:, url:, method:)
61
+ super response: response,
62
+ payload: method.request.default_payload,
63
+ expected_response: method.expected_response,
64
+ url: url,
65
+ verb: method.verb,
66
+ module_name: 'GoodVariationsModule'
67
+ end
68
+ end
69
+ end
@@ -21,7 +21,7 @@ module ApiTester
21
21
 
22
22
  method.request.fields.each do |field|
23
23
  sql_injections.each do |injection|
24
- injection_value = "#{field.default_value}#{injection}"
24
+ injection_value = "#{field.default}#{injection}"
25
25
  payload = method.request.altered_payload field_name: field.name,
26
26
  value: injection_value
27
27
  response = endpoint.call base_url: base_url,
@@ -41,7 +41,12 @@ module ApiTester
41
41
  end
42
42
 
43
43
  def self.check_response(response, endpoint)
44
- response.code == 200 || check_error(response, endpoint)
44
+ if response.code == 200 || check_error(response, endpoint)
45
+ print '.'
46
+ return true
47
+ end
48
+ print 'F'
49
+ false
45
50
  end
46
51
 
47
52
  def self.check_error(response, endpoint)
@@ -54,15 +59,16 @@ module ApiTester
54
59
  response.code == endpoint.bad_request_response.code &&
55
60
  missing_fields.size.zero? && extra_fields.size.zero?
56
61
  end
62
+
63
+ def self.order
64
+ 5
65
+ end
57
66
  end
58
67
  end
59
68
 
60
69
  # Report for InjectionModule
61
70
  class InjectionReport
62
- attr_accessor :injection_type
63
- attr_accessor :url
64
- attr_accessor :payload
65
- attr_accessor :response
71
+ attr_accessor :injection_type, :url, :payload, :response
66
72
 
67
73
  def initialize(injection_type, url, payload, response)
68
74
  self.injection_type = injection_type
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/reporter/status_code_report'
4
+ require 'api-tester/util/supported_verbs'
5
+ require 'pry'
6
+
7
+ module ApiTester
8
+ # Module checking various not found scenarios
9
+ module MissingResource
10
+ def self.go(contract)
11
+ reports = []
12
+
13
+ contract.endpoints.each do |endpoint|
14
+ endpoint.path_params.each do |path_param|
15
+ bad_resource = endpoint.relative_url.gsub("{#{path_param}}", 'gibberish')
16
+
17
+ bad_endpoint = ApiTester::Endpoint.new name: 'Bad Resource',
18
+ relative_url: bad_resource
19
+ method = ApiTester::Method.new verb: ApiTester::SupportedVerbs::GET,
20
+ response: ApiTester::Response.new(
21
+ status_code: 200
22
+ ),
23
+ request: ApiTester::Request.new
24
+ response = bad_endpoint.call base_url: contract.base_url + bad_resource,
25
+ method: method,
26
+ payload: {},
27
+ headers: contract.required_headers
28
+ test = MissingResourceTest.new response,
29
+ {},
30
+ endpoint.not_found_response,
31
+ bad_resource,
32
+ ApiTester::SupportedVerbs::GET
33
+ test.check
34
+ end
35
+ end
36
+
37
+ reports
38
+ end
39
+
40
+ def self.allowed_verbs(endpoint)
41
+ allowances = []
42
+ endpoint.methods.each do |method|
43
+ allowances << method.verb
44
+ end
45
+ allowances.uniq
46
+ end
47
+
48
+ def self.order
49
+ 4
50
+ end
51
+ end
52
+
53
+ # Test layout for Missing Resource
54
+ class MissingResourceTest < MethodCaseTest
55
+ def initialize(response, payload, expected_response, url, verb)
56
+ super response: response,
57
+ payload: payload,
58
+ expected_response: expected_response,
59
+ url: url,
60
+ verb: verb,
61
+ module_name: 'Missing Resource'
62
+ end
63
+ end
64
+ end
@@ -26,8 +26,7 @@ end
26
26
 
27
27
  # Report used by module
28
28
  class ServerBroadcastReport
29
- attr_accessor :server_info
30
- attr_accessor :server_key
29
+ attr_accessor :server_info, :server_key
31
30
 
32
31
  def initialize(server_info, server_key)
33
32
  self.server_info = server_info
@@ -9,7 +9,9 @@ module ApiTester
9
9
  def self.go(contract)
10
10
  reports = []
11
11
 
12
- contract.endpoints.each do |endpoint|
12
+ # Filtering out endpoints with ids since not a better way to check this
13
+ # Need to redesign system to handle this better
14
+ contract.endpoints.reject { |e| e.relative_url.include?('{') }.each do |endpoint|
13
15
  allowances(endpoint).each do
14
16
  reports.concat check_typo_url(contract.base_url, endpoint)
15
17
  end
@@ -10,7 +10,7 @@ module ApiTester
10
10
 
11
11
  contract.endpoints.each do |endpoint|
12
12
  endpoint.methods.each do |method|
13
- method.expected_response.body.each do |field|
13
+ method.expected_response.body.filter(&:has_key).each do |field|
14
14
  next unless field.is_seen.zero?
15
15
 
16
16
  reports << MissingFieldReport.new(url: endpoint.url,
@@ -31,14 +31,15 @@ module ApiTester
31
31
  end
32
32
 
33
33
  def print
34
+ puts ''
34
35
  if reports.size.zero?
35
36
  puts 'No issues found'
36
37
  else
37
38
  puts "Issues discovered: #{reports.size}"
38
39
  reports.each do |report|
39
40
  report.print
40
- puts '\n'
41
- puts '\n'
41
+ puts ''
42
+ puts ''
42
43
  end
43
44
  puts "Total issues: #{reports.size}"
44
45
  end
@@ -3,10 +3,7 @@
3
3
  module ApiTester
4
4
  # Report used for when response is missing a field
5
5
  class MissingFieldReport
6
- attr_accessor :url
7
- attr_accessor :verb
8
- attr_accessor :expected_field
9
- attr_accessor :description
6
+ attr_accessor :url, :verb, :expected_field, :description
10
7
 
11
8
  def initialize(url:, verb:, expected_field:, description:)
12
9
  self.url = url
@@ -3,11 +3,7 @@
3
3
  module ApiTester
4
4
  # Standard report format for differing responses
5
5
  class Report
6
- attr_accessor :description
7
- attr_accessor :url
8
- attr_accessor :request
9
- attr_accessor :expected_response
10
- attr_accessor :actual_response
6
+ attr_accessor :description, :url, :request, :expected_response, :actual_response
11
7
 
12
8
  def initialize(description:, url:, request:, expected_response:, actual_response:)
13
9
  self.description = description
@@ -22,7 +18,7 @@ module ApiTester
22
18
  puts " Requested #{url} with payload:"
23
19
  puts " #{request.to_json}"
24
20
  puts ' Expecting: '
25
- puts ' ' + expected_response.to_s
21
+ puts " #{expected_response}"
26
22
  puts ' Receiving: '
27
23
  puts " #{actual_response}"
28
24
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiTester
4
+ # Report used for when response took too long
5
+ class ResponseTimeReport
6
+ attr_accessor :url, :verb, :payload, :max_time, :actual_time, :description
7
+
8
+ def initialize(url:, verb:, payload:, max_time:, actual_time:, description:)
9
+ self.url = url
10
+ self.verb = verb
11
+ self.payload = payload
12
+ self.max_time = max_time
13
+ self.actual_time = actual_time
14
+ self.description = description
15
+ end
16
+
17
+ def print
18
+ puts "#{description}:"
19
+ puts " #{verb} #{url} took #{actual_time}ms, the max time is #{max_time}ms:"
20
+ puts ' Payload:'
21
+ puts " #{payload}"
22
+ end
23
+ end
24
+ end
@@ -5,8 +5,7 @@ require 'api-tester/reporter/report'
5
5
  module ApiTester
6
6
  # Report for when status code is different than expected
7
7
  class StatusCodeReport < Report
8
- attr_accessor :expected_status_code
9
- attr_accessor :actual_status_code
8
+ attr_accessor :expected_status_code, :actual_status_code
10
9
 
11
10
  def initialize(description:, url:, request:, expected_status_code:, actual_status_code:)
12
11
  super description: description,
@@ -3,6 +3,8 @@
3
3
  module ApiTester
4
4
  # Interface for when things need to be done before or after an api call
5
5
  class TestHelper
6
+ def initialize(url); end
7
+
6
8
  def before; end
7
9
 
8
10
  def retrieve_param(key); end
@@ -3,12 +3,12 @@
3
3
  module ApiTester
4
4
  # Class for evaluating responses against what is expected
5
5
  class ResponseEvaluator
6
- attr_accessor :response_body
7
- attr_accessor :expected_response
6
+ attr_accessor :response_body, :expected_response, :expected_fields_hash
8
7
 
9
8
  def initialize(actual_body:, expected_fields:)
10
9
  self.response_body = actual_body
11
10
  self.expected_response = expected_fields
11
+ self.expected_fields_hash = expected_field_array(expected_response.body)
12
12
  end
13
13
 
14
14
  def response_field_array
@@ -29,10 +29,6 @@ module ApiTester
29
29
  seen
30
30
  end
31
31
 
32
- def expected_fields_hash
33
- expected_field_array expected_response.body
34
- end
35
-
36
32
  def extra_fields
37
33
  response_field_array - expected_fields
38
34
  end
@@ -44,9 +40,14 @@ module ApiTester
44
40
  def expected_field_array(expected_fields)
45
41
  fields = {}
46
42
  expected_fields.each do |field|
47
- fields[field.name] = field
43
+ field_name = field.name
44
+ if field.has_key
45
+ fields[field.name] = field
46
+ else
47
+ field_name = field.type
48
+ end
48
49
  fields = fields.merge inner_expected_field(expected_fields: field.fields,
49
- name: field.name)
50
+ name: field_name)
50
51
  end
51
52
  fields
52
53
  end
@@ -55,7 +56,11 @@ module ApiTester
55
56
  fields = {}
56
57
  expected_fields.each do |field|
57
58
  inner_name = "#{name}.#{field.name}"
58
- fields[inner_name] = field
59
+ if field.has_key
60
+ fields[inner_name] = field
61
+ else
62
+ inner_name = "#{name}.#{field.type}"
63
+ end
59
64
  fields = fields.merge inner_expected_field(expected_fields: field.fields,
60
65
  name: inner_name)
61
66
  end
@@ -65,21 +70,31 @@ module ApiTester
65
70
  def field_array(object)
66
71
  fields = []
67
72
 
68
- object.each do |key, value|
69
- if key.respond_to?('each')
70
- fields.concat(field_array(key))
71
- elsif value == nil || value == 0 || value == false
72
- fields << key.to_s
73
- fields.concat(field_array(value).map { |i| "#{key}.#{i}" })
74
- elsif value.to_s[0] == '[' && value.to_s[-1] == ']' && !value.to_s.include?('=>')
75
- fields << key.to_s
76
- elsif value
77
- fields << key.to_s
78
- fields.concat(field_array(value).map { |i| "#{key}.#{i}" })
79
- else
80
- fields.concat(field_array(key))
73
+ if object.instance_of?(Array)
74
+ name = 'array'
75
+ fields.concat(field_array(object[0]).map { |i| "#{name}.#{i}" })
76
+ else
77
+ object.each do |key, value|
78
+ if key.respond_to?('each')
79
+ fields.concat(field_array(key))
80
+ elsif value == nil || value == 0 || value == false
81
+ fields << key.to_s
82
+ fields.concat(field_array(value).map { |i| "#{key}.#{i}" })
83
+ elsif value.to_s[0] == '[' && value.to_s[-1] == ']' && !value.to_s.include?('=>')
84
+ fields << key.to_s
85
+ elsif value
86
+ passed_value = value
87
+ fields << key.to_s
88
+ if value.instance_of?(Array)
89
+ passed_value = value[0]
90
+ end
91
+ fields.concat(field_array(passed_value).map { |i| "#{key}.#{i}" })
92
+ else
93
+ fields.concat(field_array(key))
94
+ end
81
95
  end
82
96
  end
97
+
83
98
  fields
84
99
  rescue NoMethodError
85
100
  fields
@@ -12,8 +12,8 @@ module ApiTester
12
12
  @hash[key]
13
13
  end
14
14
 
15
- def self.each
16
- @hash.each { |key, value| yield(key, value) }
15
+ def self.each(&block)
16
+ @hash.each(&block)
17
17
  end
18
18
 
19
19
  def self.all
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApiTester
4
- VERSION = '1.1.1'
4
+ VERSION = '1.1.2'
5
5
  end