api-tester 0.3.1 → 1.1.2
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.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +15 -0
- data/.github/workflows/dependabot.yml +29 -0
- data/.github/workflows/push.yml +39 -0
- data/.github/workflows/test.yml +31 -0
- data/.rspec +1 -0
- data/.rubocop.yml +171 -0
- data/Gemfile +2 -0
- data/Guardfile +70 -0
- data/README.md +67 -63
- data/Rakefile +8 -3
- data/api-tester.gemspec +29 -23
- data/changelog.txt +19 -0
- data/lib/api-tester/config.rb +17 -15
- data/lib/api-tester/definition/boundary_case.rb +5 -4
- data/lib/api-tester/definition/contract.rb +10 -5
- data/lib/api-tester/definition/endpoint.rb +59 -32
- data/lib/api-tester/definition/fields/array_field.rb +26 -21
- data/lib/api-tester/definition/fields/boolean_field.rb +17 -7
- data/lib/api-tester/definition/fields/email_field.rb +28 -11
- data/lib/api-tester/definition/fields/enum_field.rb +19 -12
- data/lib/api-tester/definition/fields/field.rb +52 -45
- data/lib/api-tester/definition/fields/number_field.rb +20 -6
- data/lib/api-tester/definition/fields/object_field.rb +37 -30
- data/lib/api-tester/definition/fields/plain_array_field.rb +25 -0
- data/lib/api-tester/definition/method.rb +8 -5
- data/lib/api-tester/definition/request.rb +55 -13
- data/lib/api-tester/definition/response.rb +35 -25
- data/lib/api-tester/method_case_test.rb +68 -54
- data/lib/api-tester/modules/benchmark_module.rb +35 -0
- data/lib/api-tester/modules/extra_verbs.rb +37 -10
- data/lib/api-tester/modules/format.rb +23 -8
- data/lib/api-tester/modules/good_case.rb +25 -10
- data/lib/api-tester/modules/good_variations.rb +69 -0
- data/lib/api-tester/modules/injection_module.rb +44 -23
- data/lib/api-tester/modules/missing_resource.rb +64 -0
- data/lib/api-tester/modules/required_fields.rb +51 -0
- data/lib/api-tester/modules/server_information.rb +14 -12
- data/lib/api-tester/modules/typo.rb +39 -14
- data/lib/api-tester/modules/unexpected_fields.rb +61 -0
- data/lib/api-tester/modules/unused_fields.rb +13 -7
- data/lib/api-tester/reporter/api_report.rb +25 -16
- data/lib/api-tester/reporter/missing_field_report.rb +11 -15
- data/lib/api-tester/reporter/report.rb +12 -13
- data/lib/api-tester/reporter/response_time_report.rb +24 -0
- data/lib/api-tester/reporter/status_code_report.rb +10 -4
- data/lib/api-tester/test_helper.rb +8 -6
- data/lib/api-tester/util/response_evaluator.rb +84 -56
- data/lib/api-tester/util/supported_verbs.rb +8 -5
- data/lib/api-tester/version.rb +3 -1
- data/lib/api-tester.rb +6 -3
- metadata +117 -24
- data/.travis.yml +0 -6
- data/lib/api-tester/reporter/missing_response_field_report.rb +0 -21
@@ -1,16 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'api-tester/reporter/status_code_report'
|
2
4
|
require 'api-tester/method_case_test'
|
3
5
|
|
4
6
|
module ApiTester
|
5
|
-
|
6
|
-
|
7
|
+
# Checks the good case as defined in contract
|
8
|
+
module GoodCase
|
9
|
+
def self.go(contract)
|
7
10
|
reports = []
|
8
11
|
|
9
12
|
contract.endpoints.each do |endpoint|
|
10
13
|
endpoint.methods.each do |method|
|
11
|
-
default_case = BoundaryCase.new
|
12
|
-
|
13
|
-
|
14
|
+
default_case = BoundaryCase.new description: contract.base_url + endpoint.display_url,
|
15
|
+
payload: method.request.default_payload,
|
16
|
+
headers: method.request.default_headers
|
17
|
+
response = endpoint.call base_url: contract.base_url,
|
18
|
+
method: method,
|
19
|
+
payload: default_case.payload,
|
20
|
+
headers: default_case.headers
|
21
|
+
test = GoodCaseTest.new response: response,
|
22
|
+
url: contract.base_url + endpoint.url,
|
23
|
+
method: method
|
14
24
|
reports.concat test.check
|
15
25
|
end
|
16
26
|
end
|
@@ -18,14 +28,19 @@ module ApiTester
|
|
18
28
|
end
|
19
29
|
|
20
30
|
def self.order
|
21
|
-
|
31
|
+
1
|
22
32
|
end
|
23
33
|
end
|
24
34
|
|
25
|
-
|
35
|
+
# Test layout used by module
|
26
36
|
class GoodCaseTest < MethodCaseTest
|
27
|
-
|
28
|
-
|
29
|
-
|
37
|
+
def initialize(response:, url:, method:)
|
38
|
+
super response: response,
|
39
|
+
payload: method.request.default_payload,
|
40
|
+
expected_response: method.expected_response,
|
41
|
+
url: url,
|
42
|
+
verb: method.verb,
|
43
|
+
module_name: 'GoodCaseModule'
|
44
|
+
end
|
30
45
|
end
|
31
46
|
end
|
@@ -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
|
@@ -1,29 +1,39 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'injection_vulnerability_library'
|
2
4
|
|
3
5
|
module ApiTester
|
6
|
+
# Tests injection cases
|
4
7
|
module InjectionModule
|
5
|
-
def self.go
|
8
|
+
def self.go(contract)
|
6
9
|
reports = []
|
7
10
|
contract.endpoints.each do |endpoint|
|
8
11
|
endpoint.methods.each do |method|
|
9
|
-
reports.concat inject_payload endpoint, method
|
12
|
+
reports.concat inject_payload contract.base_url, endpoint, method
|
10
13
|
end
|
11
14
|
end
|
12
15
|
reports
|
13
16
|
end
|
14
17
|
|
15
|
-
def self.inject_payload endpoint, method
|
18
|
+
def self.inject_payload(base_url, endpoint, method)
|
16
19
|
reports = []
|
17
20
|
sql_injections = InjectionVulnerabilityLibrary.sql_vulnerabilities
|
18
21
|
|
19
22
|
method.request.fields.each do |field|
|
20
23
|
sql_injections.each do |injection|
|
21
|
-
injection_value = "#{field.
|
22
|
-
payload = method.request.altered_payload
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
injection_value = "#{field.default}#{injection}"
|
25
|
+
payload = method.request.altered_payload field_name: field.name,
|
26
|
+
value: injection_value
|
27
|
+
response = endpoint.call base_url: base_url,
|
28
|
+
method: method,
|
29
|
+
payload: payload,
|
30
|
+
headers: method.request.default_headers
|
31
|
+
next if check_response(response, endpoint)
|
32
|
+
|
33
|
+
reports << InjectionReport.new('sql',
|
34
|
+
endpoint.url,
|
35
|
+
payload,
|
36
|
+
response)
|
27
37
|
end
|
28
38
|
end
|
29
39
|
|
@@ -31,25 +41,36 @@ module ApiTester
|
|
31
41
|
end
|
32
42
|
|
33
43
|
def self.check_response(response, endpoint)
|
34
|
-
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
|
35
50
|
end
|
36
51
|
|
37
|
-
def self.check_error
|
38
|
-
evaluator = ApiTester::ResponseEvaluator.new
|
52
|
+
def self.check_error(response, endpoint)
|
53
|
+
evaluator = ApiTester::ResponseEvaluator.new(
|
54
|
+
actual_body: response.body,
|
55
|
+
expected_fields: endpoint.bad_request_response
|
56
|
+
)
|
39
57
|
missing_fields = evaluator.missing_fields
|
40
58
|
extra_fields = evaluator.extra_fields
|
41
|
-
response.code == endpoint.bad_request_response.code &&
|
59
|
+
response.code == endpoint.bad_request_response.code &&
|
60
|
+
missing_fields.size.zero? && extra_fields.size.zero?
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.order
|
64
|
+
5
|
42
65
|
end
|
43
66
|
end
|
44
67
|
end
|
45
68
|
|
69
|
+
# Report for InjectionModule
|
46
70
|
class InjectionReport
|
47
|
-
attr_accessor :injection_type
|
48
|
-
attr_accessor :url
|
49
|
-
attr_accessor :payload
|
50
|
-
attr_accessor :response
|
71
|
+
attr_accessor :injection_type, :url, :payload, :response
|
51
72
|
|
52
|
-
def initialize
|
73
|
+
def initialize(injection_type, url, payload, response)
|
53
74
|
self.injection_type = injection_type
|
54
75
|
self.url = url
|
55
76
|
self.payload = payload
|
@@ -57,10 +78,10 @@ class InjectionReport
|
|
57
78
|
end
|
58
79
|
|
59
80
|
def print
|
60
|
-
puts "Found potential #{
|
61
|
-
puts " Requested #{
|
62
|
-
puts " #{
|
81
|
+
puts "Found potential #{injection_type}: "
|
82
|
+
puts " Requested #{url} with payload:"
|
83
|
+
puts " #{payload}"
|
63
84
|
puts ' Received: '
|
64
|
-
puts " #{
|
85
|
+
puts " #{response}"
|
65
86
|
end
|
66
87
|
end
|
@@ -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
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiTester
|
4
|
+
# Ensures the fields marked as required in contract are guarded
|
5
|
+
module RequiredFields
|
6
|
+
def self.go(contract)
|
7
|
+
reports = []
|
8
|
+
contract.endpoints.each do |endpoint|
|
9
|
+
endpoint.methods.each do |method|
|
10
|
+
request_def = method.request
|
11
|
+
required_fields = request_def.fields.keep_if(&:required)
|
12
|
+
combinations = (1..required_fields.size).flat_map { |size| required_fields.combination(size).to_a }
|
13
|
+
combinations.each do |remove_fields|
|
14
|
+
fields = remove_fields.map do |field|
|
15
|
+
{ name: field.name, value: nil }
|
16
|
+
end
|
17
|
+
payload = request_def.altered_payload_with fields
|
18
|
+
response = endpoint.call base_url: contract.base_url,
|
19
|
+
method: method,
|
20
|
+
payload: payload,
|
21
|
+
headers: request_def.default_headers
|
22
|
+
test = RequiredFieldsTest.new response: response,
|
23
|
+
payload: payload,
|
24
|
+
expected_response: endpoint.bad_request_response,
|
25
|
+
url: endpoint.url,
|
26
|
+
verb: method.verb
|
27
|
+
reports.concat test.check
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
reports
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.order
|
36
|
+
5
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Test layout used for RequiredFieldsModule
|
41
|
+
class RequiredFieldsTest < MethodCaseTest
|
42
|
+
def initialize(response:, payload:, expected_response:, url:, verb:)
|
43
|
+
super response: response,
|
44
|
+
payload: payload,
|
45
|
+
expected_response: expected_response,
|
46
|
+
url: url,
|
47
|
+
verb: verb,
|
48
|
+
module_name: 'RequiredFieldsModule'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,15 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ApiTester
|
4
|
+
# Module for ensuring the server isn't broadcasting information about itself
|
4
5
|
module ServerInformation
|
5
|
-
def self.go
|
6
|
+
def self.go(contract)
|
6
7
|
reports = []
|
7
8
|
endpoint = contract.endpoints[0]
|
8
|
-
response = endpoint.default_call
|
9
|
+
response = endpoint.default_call contract.base_url
|
9
10
|
|
10
|
-
[
|
11
|
-
if response.headers[
|
12
|
-
reports << ServerBroadcastReport.new(response.headers[
|
11
|
+
%i[server x_powered_by x_aspnetmvc_version x_aspnet_version].each do |key|
|
12
|
+
if response.headers[key]
|
13
|
+
reports << ServerBroadcastReport.new(response.headers[key],
|
14
|
+
key)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -22,18 +24,18 @@ module ApiTester
|
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
27
|
+
# Report used by module
|
25
28
|
class ServerBroadcastReport
|
26
|
-
attr_accessor :server_info
|
27
|
-
attr_accessor :server_key
|
29
|
+
attr_accessor :server_info, :server_key
|
28
30
|
|
29
|
-
def initialize
|
31
|
+
def initialize(server_info, server_key)
|
30
32
|
self.server_info = server_info
|
31
33
|
self.server_key = server_key
|
32
34
|
end
|
33
35
|
|
34
36
|
def print
|
35
|
-
puts
|
36
|
-
puts " #{
|
37
|
-
puts " as #{
|
37
|
+
puts 'Found server information being broadcast in headers:'
|
38
|
+
puts " #{server_info}"
|
39
|
+
puts " as #{server_key}"
|
38
40
|
end
|
39
41
|
end
|
@@ -1,35 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'api-tester/reporter/status_code_report'
|
2
4
|
require 'api-tester/util/supported_verbs'
|
3
5
|
|
4
6
|
module ApiTester
|
5
|
-
|
6
|
-
|
7
|
+
# Module checking various not found scenarios
|
8
|
+
module Typo
|
9
|
+
def self.go(contract)
|
7
10
|
reports = []
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
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|
|
15
|
+
allowances(endpoint).each do
|
16
|
+
reports.concat check_typo_url(contract.base_url, endpoint)
|
12
17
|
end
|
13
18
|
end
|
14
19
|
|
15
20
|
reports
|
16
21
|
end
|
17
22
|
|
18
|
-
def self.check_typo_url endpoint
|
23
|
+
def self.check_typo_url(base_url, endpoint)
|
19
24
|
bad_url = "#{endpoint.url}gibberishadsfasdf"
|
20
|
-
bad_endpoint = ApiTester::Endpoint.new
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
bad_endpoint = ApiTester::Endpoint.new name: 'Bad URL',
|
26
|
+
relative_url: bad_url
|
27
|
+
typo_case = BoundaryCase.new description: 'Typo URL check',
|
28
|
+
payload: {},
|
29
|
+
headers: {}
|
30
|
+
method = ApiTester::Method.new verb: ApiTester::SupportedVerbs::GET,
|
31
|
+
response: ApiTester::Response.new(
|
32
|
+
status_code: 200
|
33
|
+
),
|
34
|
+
request: ApiTester::Request.new
|
35
|
+
response = bad_endpoint.call base_url: base_url,
|
36
|
+
method: method,
|
37
|
+
payload: typo_case.payload,
|
38
|
+
headers: typo_case.headers
|
24
39
|
|
25
|
-
test = TypoClass.new response,
|
40
|
+
test = TypoClass.new response,
|
41
|
+
typo_case.payload,
|
42
|
+
endpoint.not_found_response,
|
43
|
+
bad_url,
|
44
|
+
ApiTester::SupportedVerbs::GET
|
26
45
|
test.check
|
27
46
|
end
|
28
47
|
|
29
48
|
def self.allowances(endpoint)
|
30
49
|
allowances = []
|
31
50
|
endpoint.methods.each do |method|
|
32
|
-
|
51
|
+
allowances << method.verb
|
33
52
|
end
|
34
53
|
allowances.uniq
|
35
54
|
end
|
@@ -39,9 +58,15 @@ module ApiTester
|
|
39
58
|
end
|
40
59
|
end
|
41
60
|
|
61
|
+
# Test layout for TypoModule
|
42
62
|
class TypoClass < MethodCaseTest
|
43
|
-
def initialize
|
44
|
-
super response
|
63
|
+
def initialize(response, payload, expected_response, url, verb)
|
64
|
+
super response: response,
|
65
|
+
payload: payload,
|
66
|
+
expected_response: expected_response,
|
67
|
+
url: url,
|
68
|
+
verb: verb,
|
69
|
+
module_name: 'TypoModule'
|
45
70
|
end
|
46
71
|
end
|
47
72
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
module ApiTester
|
6
|
+
# Module checking nothing shows up in response which is not defined in contract
|
7
|
+
module UnexpectedFields
|
8
|
+
def self.go(contract)
|
9
|
+
reports = []
|
10
|
+
|
11
|
+
contract.endpoints.each do |endpoint|
|
12
|
+
endpoint.methods.each do |method|
|
13
|
+
default_case = BoundaryCase.new description: endpoint.url,
|
14
|
+
payload: method.request.default_payload,
|
15
|
+
headers: method.request.default_headers
|
16
|
+
response = endpoint.call base_url: contract.base_url,
|
17
|
+
method: method,
|
18
|
+
payload: default_case.payload,
|
19
|
+
headers: default_case.headers
|
20
|
+
test = UnexpectedFieldsTest.new response, endpoint.url, method
|
21
|
+
reports.concat test.check
|
22
|
+
end
|
23
|
+
end
|
24
|
+
reports
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.order
|
28
|
+
90
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Test layout for UnexpectedFields module
|
33
|
+
class UnexpectedFieldsTest < MethodCaseTest
|
34
|
+
def initialize(response, url, method)
|
35
|
+
super response: response,
|
36
|
+
payload: method.request.default_payload,
|
37
|
+
expected_response: method.expected_response,
|
38
|
+
url: url,
|
39
|
+
verb: method.verb,
|
40
|
+
module_name: 'UnexpectedFieldsModule'
|
41
|
+
end
|
42
|
+
|
43
|
+
def check
|
44
|
+
evaluator = ApiTester::ResponseEvaluator.new actual_body: json_parse(response.body),
|
45
|
+
expected_fields: expected_response
|
46
|
+
response_fields = evaluator.response_field_array
|
47
|
+
expected_fields = evaluator.expected_fields
|
48
|
+
increment_fields evaluator.seen_fields
|
49
|
+
extra = response_fields - expected_fields
|
50
|
+
extra.each do |extra_field|
|
51
|
+
report = Report.new description: "UnexpectedFieldsModule - Found unexpected field #{extra_field}",
|
52
|
+
url: url,
|
53
|
+
request: payload,
|
54
|
+
expected_response: expected_response,
|
55
|
+
actual_response: response
|
56
|
+
reports << report
|
57
|
+
end
|
58
|
+
reports
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -1,16 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'api-tester/reporter/missing_field_report'
|
2
4
|
|
3
5
|
module ApiTester
|
4
|
-
|
5
|
-
|
6
|
+
# Ensures all fields defined in contract are returned during test suite
|
7
|
+
module UnusedFields
|
8
|
+
def self.go(contract)
|
6
9
|
reports = []
|
7
10
|
|
8
11
|
contract.endpoints.each do |endpoint|
|
9
12
|
endpoint.methods.each do |method|
|
10
|
-
method.expected_response.body.each do |field|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
method.expected_response.body.filter(&:has_key).each do |field|
|
14
|
+
next unless field.is_seen.zero?
|
15
|
+
|
16
|
+
reports << MissingFieldReport.new(url: endpoint.url,
|
17
|
+
verb: method.verb,
|
18
|
+
expected_field: field.name,
|
19
|
+
description: 'UnusedFieldsModule')
|
14
20
|
end
|
15
21
|
end
|
16
22
|
end
|
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'api-tester/reporter/report'
|
2
4
|
|
3
5
|
module ApiTester
|
6
|
+
# class for dealing with reports generated by the modules during test suite
|
4
7
|
class ApiReport
|
5
8
|
attr_accessor :reports
|
6
9
|
|
@@ -8,31 +11,37 @@ module ApiTester
|
|
8
11
|
self.reports = []
|
9
12
|
end
|
10
13
|
|
11
|
-
def add_new
|
12
|
-
report = Report.new description,
|
13
|
-
|
14
|
+
def add_new(url:, request:, expected_response:, actual_response:, description: 'case')
|
15
|
+
report = Report.new description,
|
16
|
+
url,
|
17
|
+
request,
|
18
|
+
expected_response,
|
19
|
+
actual_response
|
20
|
+
reports << report
|
14
21
|
end
|
15
22
|
|
16
|
-
def add_new_report
|
17
|
-
|
23
|
+
def add_new_report(report)
|
24
|
+
reports << report
|
18
25
|
end
|
19
26
|
|
20
|
-
def add_reports
|
21
|
-
|
27
|
+
def add_reports(reports)
|
28
|
+
reports.each do |report|
|
29
|
+
add_new_report(report)
|
30
|
+
end
|
22
31
|
end
|
23
32
|
|
24
33
|
def print
|
25
|
-
|
26
|
-
|
27
|
-
|
34
|
+
puts ''
|
35
|
+
if reports.size.zero?
|
36
|
+
puts 'No issues found'
|
37
|
+
else
|
38
|
+
puts "Issues discovered: #{reports.size}"
|
39
|
+
reports.each do |report|
|
28
40
|
report.print
|
29
|
-
puts
|
30
|
-
puts
|
41
|
+
puts ''
|
42
|
+
puts ''
|
31
43
|
end
|
32
|
-
puts ""
|
33
|
-
puts "Issues discovered: #{self.reports.size}"
|
34
|
-
else
|
35
|
-
puts "No issues found"
|
44
|
+
puts "Total issues: #{reports.size}"
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|