api-tester 0.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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