api-tester 0.1.0 → 1.1.1

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.
Files changed (79) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +15 -0
  3. data/.github/workflows/push.yml +39 -0
  4. data/.github/workflows/test.yml +31 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +61 -0
  7. data/Gemfile +2 -0
  8. data/Guardfile +70 -0
  9. data/README.md +106 -74
  10. data/Rakefile +8 -3
  11. data/api-tester.gemspec +31 -23
  12. data/changelog.txt +35 -0
  13. data/lib/api-tester.rb +15 -0
  14. data/lib/api-tester/config.rb +43 -0
  15. data/lib/api-tester/definition/boundary_case.rb +16 -0
  16. data/lib/api-tester/definition/contract.rb +20 -0
  17. data/lib/api-tester/definition/endpoint.rb +84 -0
  18. data/lib/api-tester/definition/fields/array_field.rb +47 -0
  19. data/lib/api-tester/definition/fields/boolean_field.rb +23 -0
  20. data/lib/api-tester/definition/fields/email_field.rb +25 -0
  21. data/lib/api-tester/definition/fields/enum_field.rb +32 -0
  22. data/lib/api-tester/definition/fields/field.rb +50 -0
  23. data/lib/api-tester/definition/fields/number_field.rb +22 -0
  24. data/lib/api-tester/definition/fields/object_field.rb +47 -0
  25. data/lib/api-tester/definition/fields/plain_array_field.rb +25 -0
  26. data/lib/api-tester/definition/method.rb +22 -0
  27. data/lib/api-tester/definition/request.rb +96 -0
  28. data/lib/api-tester/definition/response.rb +39 -0
  29. data/lib/api-tester/method_case_test.rb +83 -0
  30. data/lib/api-tester/modules/extra_verbs.rb +53 -0
  31. data/lib/api-tester/modules/format.rb +47 -0
  32. data/lib/api-tester/modules/good_case.rb +46 -0
  33. data/lib/api-tester/modules/injection_module.rb +81 -0
  34. data/lib/api-tester/modules/required_fields.rb +51 -0
  35. data/lib/api-tester/modules/server_information.rb +42 -0
  36. data/lib/api-tester/modules/typo.rb +70 -0
  37. data/lib/api-tester/modules/unexpected_fields.rb +61 -0
  38. data/lib/api-tester/modules/unused_fields.rb +31 -0
  39. data/lib/api-tester/reporter/api_report.rb +47 -0
  40. data/lib/api-tester/reporter/missing_field_report.rb +24 -0
  41. data/lib/api-tester/reporter/report.rb +30 -0
  42. data/lib/api-tester/reporter/status_code_report.rb +21 -0
  43. data/lib/api-tester/test_helper.rb +12 -0
  44. data/lib/api-tester/util/response_evaluator.rb +88 -0
  45. data/lib/api-tester/util/supported_verbs.rb +39 -0
  46. data/lib/api-tester/version.rb +5 -0
  47. metadata +159 -42
  48. data/.travis.yml +0 -6
  49. data/lib/tester.rb +0 -7
  50. data/lib/tester/api_tester.rb +0 -50
  51. data/lib/tester/definition/api_contract.rb +0 -13
  52. data/lib/tester/definition/api_method.rb +0 -11
  53. data/lib/tester/definition/boundary_case.rb +0 -11
  54. data/lib/tester/definition/endpoint.rb +0 -57
  55. data/lib/tester/definition/fields/array_field.rb +0 -44
  56. data/lib/tester/definition/fields/boolean_field.rb +0 -18
  57. data/lib/tester/definition/fields/email_field.rb +0 -20
  58. data/lib/tester/definition/fields/enum_field.rb +0 -27
  59. data/lib/tester/definition/fields/field.rb +0 -47
  60. data/lib/tester/definition/fields/number_field.rb +0 -17
  61. data/lib/tester/definition/fields/object_field.rb +0 -42
  62. data/lib/tester/definition/request.rb +0 -49
  63. data/lib/tester/definition/response.rb +0 -34
  64. data/lib/tester/method_case_test.rb +0 -67
  65. data/lib/tester/modules/extra_verbs.rb +0 -25
  66. data/lib/tester/modules/format.rb +0 -26
  67. data/lib/tester/modules/good_case.rb +0 -29
  68. data/lib/tester/modules/module.rb +0 -18
  69. data/lib/tester/modules/typo.rb +0 -41
  70. data/lib/tester/modules/unused_fields.rb +0 -22
  71. data/lib/tester/reporter/api_report.rb +0 -33
  72. data/lib/tester/reporter/missing_field_report.rb +0 -23
  73. data/lib/tester/reporter/missing_response_field_report.rb +0 -19
  74. data/lib/tester/reporter/report.rb +0 -25
  75. data/lib/tester/reporter/status_code_report.rb +0 -12
  76. data/lib/tester/test_helper.rb +0 -10
  77. data/lib/tester/util/response_evaluator.rb +0 -73
  78. data/lib/tester/util/supported_verbs.rb +0 -34
  79. data/lib/tester/version.rb +0 -3
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/fields/field'
4
+
5
+ module ApiTester
6
+ # Class for defining numeric fields in contracts
7
+ class NumberField < Field
8
+ def initialize(name:, default_value: 5, required: false)
9
+ super name: name, default_value: default_value, required: required
10
+ end
11
+
12
+ def negative_boundary_values
13
+ super +
14
+ [
15
+ 'string',
16
+ true,
17
+ false,
18
+ {}
19
+ ]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/fields/field'
4
+
5
+ module ApiTester
6
+ # Class for defining objects in a contract
7
+ class ObjectField < Field
8
+ attr_accessor :fields
9
+
10
+ def initialize(name:, required: false)
11
+ super name: name, required: required
12
+ self.fields = []
13
+ end
14
+
15
+ def with_field(new_field)
16
+ fields << new_field
17
+ self
18
+ end
19
+
20
+ def subfields?
21
+ true
22
+ end
23
+
24
+ def default_value
25
+ obj = {}
26
+
27
+ fields.each do |field|
28
+ obj[field.name] = field.default_value
29
+ end
30
+
31
+ obj
32
+ end
33
+
34
+ def negative_boundary_values
35
+ super +
36
+ [
37
+ 'string',
38
+ [],
39
+ 123,
40
+ 1,
41
+ 0,
42
+ true,
43
+ false
44
+ ]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/fields/field'
4
+
5
+ module ApiTester
6
+ # Class for defining plain arrays
7
+ class PlainArrayField < Field
8
+ def initialize(name:, default_value: [], required: false)
9
+ super name: name, default_value: default_value, required: required
10
+ end
11
+
12
+ def negative_boundary_values
13
+ super +
14
+ [
15
+ 'string',
16
+ 123,
17
+ 0,
18
+ 1,
19
+ {},
20
+ true,
21
+ false
22
+ ]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiTester
4
+ # Class for defining methods as part of an endpoint
5
+ class Method
6
+ attr_accessor :request
7
+ attr_accessor :expected_response
8
+ attr_accessor :verb
9
+
10
+ def initialize(verb:, response:, request:)
11
+ self.verb = verb
12
+ self.request = request
13
+ self.expected_response = response
14
+ end
15
+
16
+ def default_request
17
+ { method: verb,
18
+ payload: request.default_payload,
19
+ headers: request.default_headers }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/boundary_case'
4
+
5
+ module ApiTester
6
+ # Class for defining requests in a contract
7
+ class Request
8
+ attr_accessor :definition
9
+ attr_accessor :header_fields
10
+ attr_accessor :fields
11
+ attr_accessor :query_params
12
+
13
+ def initialize
14
+ self.fields = []
15
+ self.header_fields = []
16
+ self.query_params = []
17
+ end
18
+
19
+ def add_field(new_field)
20
+ fields << new_field
21
+ self
22
+ end
23
+
24
+ def add_query_param(new_query_param)
25
+ query_params << new_query_param
26
+ self
27
+ end
28
+
29
+ def default_query
30
+ query_params.map { |param| "#{param.name}=#{param.default_value}" }.join('&')
31
+ end
32
+
33
+ def add_header_field(new_header)
34
+ header_fields << new_header
35
+ self
36
+ end
37
+
38
+ def payload
39
+ response = {}
40
+ fields.each do |field|
41
+ if field.required == true
42
+ response[field.name] = field.default_value
43
+ end
44
+ end
45
+ response
46
+ end
47
+
48
+ def default_payload
49
+ payload
50
+ end
51
+
52
+ def default_headers
53
+ if header_fields != []
54
+ headers
55
+ else
56
+ { content_type: :json, accept: :json }
57
+ end
58
+ end
59
+
60
+ def headers
61
+ header_response = {}
62
+ header_fields.each do |header_field|
63
+ header_response[header_field.name] = header_field.default_value
64
+ end
65
+ header_response
66
+ end
67
+
68
+ def cases
69
+ boundary_cases = []
70
+ fields.each do |field|
71
+ field.negative_boundary_values.each do |value|
72
+ bcase = BoundaryCase.new description: "Setting #{field.name} to #{value}",
73
+ payload: altered_payload(field_name: field.name,
74
+ value: value),
75
+ headers: default_headers
76
+ boundary_cases.push bcase
77
+ end
78
+ end
79
+ boundary_cases
80
+ end
81
+
82
+ def altered_payload(field_name:, value:)
83
+ body = payload
84
+ body[field_name] = value
85
+ body
86
+ end
87
+
88
+ def altered_payload_with(fields)
89
+ body = payload
90
+ fields.each do |field|
91
+ body[field[:name]] = field[:value]
92
+ end
93
+ body
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiTester
4
+ # Class for defining expected responses
5
+ class Response
6
+ attr_accessor :code
7
+ attr_accessor :body
8
+
9
+ def initialize(status_code: 200)
10
+ self.code = status_code
11
+ self.body = []
12
+ end
13
+
14
+ def add_field(new_field)
15
+ body << new_field
16
+ self
17
+ end
18
+
19
+ def to_s
20
+ des = {}
21
+ body.map do |f|
22
+ des[f.name] = field_display f
23
+ end
24
+ des.to_json
25
+ end
26
+
27
+ def field_display(field)
28
+ des = field.display_class
29
+ if field.subfields?
30
+ des = {}
31
+ field.fields.map do |f|
32
+ des[f.name] = field_display f
33
+ end
34
+ des.to_json
35
+ end
36
+ des
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/util/response_evaluator.rb'
4
+
5
+ module ApiTester
6
+ # Class for testing methods
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
14
+
15
+ def initialize(response:, payload:, expected_response:, url:, verb:, module_name:)
16
+ self.payload = payload
17
+ self.response = response
18
+ self.expected_response = expected_response
19
+ self.reports = []
20
+ self.url = "#{verb} #{url}"
21
+ self.module_name = module_name
22
+ end
23
+
24
+ def response_code_report
25
+ report = StatusCodeReport.new description: "#{module_name} - Incorrect response code",
26
+ url: url,
27
+ request: payload,
28
+ expected_status_code: expected_response.code,
29
+ actual_status_code: "#{response.code} : #{response.body}"
30
+ reports << report
31
+ nil
32
+ end
33
+
34
+ def missing_field_report(field)
35
+ report = Report.new description: "#{module_name} - Missing field #{field}",
36
+ url: url,
37
+ request: payload,
38
+ expected_response: expected_response,
39
+ actual_response: response
40
+ reports << report
41
+ nil
42
+ end
43
+
44
+ def extra_field_report(field)
45
+ report = Report.new description: "#{module_name} - Found extra field #{field}",
46
+ url: url,
47
+ request: payload,
48
+ expected_response: expected_response,
49
+ actual_response: response
50
+ reports << report
51
+ nil
52
+ end
53
+
54
+ def check
55
+ if check_response_code
56
+ evaluator = ApiTester::ResponseEvaluator.new actual_body: json_parse(response.body),
57
+ expected_fields: expected_response
58
+ evaluator.missing_fields.map { |field| missing_field_report(field) }
59
+ evaluator.extra_fields.map { |field| extra_field_report(field) }
60
+ increment_fields evaluator.seen_fields
61
+ end
62
+ reports
63
+ end
64
+
65
+ def check_response_code
66
+ if response.code != expected_response.code
67
+ response_code_report
68
+ return false
69
+ end
70
+ true
71
+ end
72
+
73
+ def increment_fields(seen_fields)
74
+ seen_fields.each(&:seen)
75
+ end
76
+
77
+ def json_parse(body)
78
+ JSON.parse!(body)
79
+ rescue JSON::ParserError
80
+ body
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/util/supported_verbs'
4
+
5
+ module ApiTester
6
+ # Check verbs not explicitly defined in contract
7
+ module ExtraVerbs
8
+ def self.go(contract)
9
+ reports = []
10
+
11
+ contract.endpoints.each do |endpoint|
12
+ extras = ApiTester::SupportedVerbs.all - endpoint.verbs
13
+ headers = endpoint.methods[0].request.default_headers
14
+ extras.each do |verb|
15
+ verb_case = BoundaryCase.new description: "Verb check with #{verb} for #{endpoint.name}",
16
+ payload: {},
17
+ headers: headers
18
+ method = ApiTester::Method.new verb: verb,
19
+ response: ApiTester::Response.new,
20
+ request: ApiTester::Request.new
21
+ response = endpoint.call base_url: contract.base_url,
22
+ method: method,
23
+ payload: verb_case.payload,
24
+ headers: verb_case.headers
25
+ test = VerbClass.new response: response,
26
+ payload: verb_case.payload,
27
+ expected_response: endpoint.not_allowed_response,
28
+ url: endpoint.url,
29
+ verb: verb
30
+ reports.concat test.check
31
+ end
32
+ end
33
+
34
+ reports
35
+ end
36
+
37
+ def self.order
38
+ 3
39
+ end
40
+ end
41
+
42
+ # Test template used for module
43
+ class VerbClass < MethodCaseTest
44
+ def initialize(response:, payload:, expected_response:, url:, verb:)
45
+ super response: response,
46
+ payload: payload,
47
+ expected_response: expected_response,
48
+ url: url,
49
+ verb: verb,
50
+ module_name: 'VerbModule'
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,47 @@
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 format constraints defined in contract
8
+ module Format
9
+ def self.go(contract)
10
+ reports = []
11
+ contract.endpoints.each do |endpoint|
12
+ endpoint.methods.each do |method|
13
+ cases = method.request.cases
14
+ cases.each do |format_case|
15
+ response = endpoint.call base_url: contract.base_url,
16
+ method: method,
17
+ payload: format_case.payload,
18
+ headers: format_case.headers
19
+ test = FormatTest.new response: response,
20
+ payload: format_case.payload,
21
+ expected_response: endpoint.bad_request_response,
22
+ url: endpoint.url,
23
+ verb: method.verb
24
+ reports.concat test.check
25
+ end
26
+ end
27
+ end
28
+ reports
29
+ end
30
+
31
+ def self.order
32
+ 2
33
+ end
34
+ end
35
+
36
+ # Test layout used by Format module
37
+ class FormatTest < MethodCaseTest
38
+ def initialize(response:, payload:, expected_response:, url:, verb:)
39
+ super response: response,
40
+ payload: payload,
41
+ expected_response: expected_response,
42
+ url: url,
43
+ verb: verb,
44
+ module_name: 'FormatModule'
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,46 @@
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 GoodCase
9
+ def self.go(contract)
10
+ reports = []
11
+
12
+ contract.endpoints.each do |endpoint|
13
+ endpoint.methods.each do |method|
14
+ default_case = BoundaryCase.new description: contract.base_url + endpoint.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
24
+ reports.concat test.check
25
+ end
26
+ end
27
+ reports
28
+ end
29
+
30
+ def self.order
31
+ 1
32
+ end
33
+ end
34
+
35
+ # Test layout used by module
36
+ class GoodCaseTest < MethodCaseTest
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
45
+ end
46
+ end