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
data/Rakefile CHANGED
@@ -1,6 +1,11 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new(:rubocop) do |t|
6
+ t.options = ['--display-cop-names']
7
+ end
3
8
 
4
9
  RSpec::Core::RakeTask.new(:spec)
5
10
 
6
- task :default => :spec
11
+ task :default => [:rubocop, :spec]
@@ -1,39 +1,47 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'tester/version'
4
+ require 'api-tester/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "api-tester"
8
- spec.version = Tester::VERSION
9
- spec.authors = ["arane"]
10
- spec.email = ["arane9@gmail.com"]
7
+ spec.name = 'api-tester'
8
+ spec.version = ApiTester::VERSION
9
+ spec.authors = ['arane']
10
+ spec.email = ['arane9@gmail.com']
11
11
 
12
- spec.summary = %q{Tool to help test APIs}
13
- spec.description = %q{Tool to test APIs which will eventually do boundary testing and other sorts of testing automatically given a contract}
14
- spec.homepage = "https://github.com/araneforseti/api-tester"
15
- spec.license = "MIT"
12
+ spec.summary = 'Tool to help test APIs'
13
+ spec.description = 'Tool to test APIs which will eventually do boundary testing and other sorts of testing automatically given a contract'
14
+ spec.homepage = 'https://github.com/araneforseti/api-tester'
15
+ spec.license = 'MIT'
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
18
  # to allow pushing to a single host or delete this section to allow pushing to any host.
19
- if spec.respond_to?(:metadata)
20
- spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
21
- else
22
- raise "RubyGems 2.0 or newer is required to protect against " \
23
- "public gem pushes."
24
- end
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
21
+ # else
22
+ # raise 'RubyGems 2.0 or newer is required to protect against ' \
23
+ # 'public gem pushes.'
24
+ # end
25
25
 
26
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
27
  f.match(%r{^(test|spec|features)/})
28
28
  end
29
- spec.bindir = "exe"
29
+ spec.bindir = 'exe'
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
- spec.require_paths = ["lib"]
31
+ spec.require_paths = ['lib']
32
32
 
33
- spec.add_development_dependency "bundler", "~> 1.13"
34
- spec.add_development_dependency "rake", "~> 10.0"
35
- spec.add_development_dependency "rspec", "~> 3.0"
36
- spec.add_development_dependency "webmock", "~> 3.4"
33
+ spec.add_development_dependency 'bundler'
34
+ spec.add_development_dependency 'bundler-audit', '~>0.7.0'
35
+ spec.add_development_dependency 'guard-rspec', '~> 4.7.3'
36
+ spec.add_development_dependency 'pry', '~> 0.11'
37
+ spec.add_development_dependency 'rake', '~> 13.0.1'
38
+ spec.add_development_dependency 'require_all', '~>3.0.0'
39
+ spec.add_development_dependency 'rspec', '~> 3.0'
40
+ spec.add_development_dependency 'rubocop', '~> 0.93.0'
41
+ spec.add_development_dependency 'terminal-notifier', '~> 2.0.0'
42
+ spec.add_development_dependency 'terminal-notifier-guard', '~> 1.7.0'
43
+ spec.add_development_dependency 'webmock', '~> 3.4'
37
44
 
38
- spec.add_runtime_dependency "rest-client", "~> 2.0"
45
+ spec.add_runtime_dependency 'injection_vulnerability_library', '0.0.2'
46
+ spec.add_runtime_dependency 'rest-client', '~> 2.0'
39
47
  end
@@ -1,3 +1,38 @@
1
+ 2.0.0
2
+
3
+ - Added Required Fields module
4
+ - Added Unexpected Fields module
5
+ - Adding query params to definition
6
+ - Moved base_url to contract
7
+ - Changed endpoints to be relative urls
8
+ - Removed missing field report as it was unused
9
+ - Changing everything with more than one parameter to named parameters
10
+
11
+ 1.0.0
12
+
13
+ - Switching to using named variables to make future changes less breaky
14
+
15
+ 0.3.2
16
+
17
+ - Fixing some issues with how config adds modules
18
+ - Fixing README for same module issue as config
19
+
20
+ 0.3.1
21
+
22
+ - Adding ability to change headers for request objects
23
+ - Removing the reports from the sub modules and making them just return their reports
24
+ - Adjusted modules to use the full contract instead of endpoints
25
+ - Created module to check for server information being broadcast
26
+
27
+ 0.3.0
28
+
29
+ - Making modules actually be modules
30
+
31
+ 0.2.0
32
+
33
+ - Finally fixing the naming scheme
34
+ - Splitting configuration into its own class
35
+
1
36
  0.1.0
2
37
 
3
38
  - Moving call to endpoint for path param support
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Tool for testing through API definitions
4
+ module ApiTester
5
+ def self.go(contract, config)
6
+ reporter = config.reporter
7
+
8
+ config.modules.sort_by(&:order).each do |mod|
9
+ reporter.add_reports mod.go contract
10
+ end
11
+
12
+ reporter.print
13
+ reporter.reports.size.zero?
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/reporter/api_report'
4
+
5
+ module ApiTester
6
+ # Config class for changing how the tool operates
7
+ class Config
8
+ attr_accessor :reporter
9
+ attr_accessor :modules
10
+
11
+ def initialize(reporter: ApiTester::ApiReport.new)
12
+ self.reporter = reporter
13
+ self.modules = []
14
+ end
15
+
16
+ def with_reporter(reporter)
17
+ self.reporter = reporter
18
+ self
19
+ end
20
+
21
+ def with_module(new_module)
22
+ modules << new_module
23
+ self
24
+ end
25
+
26
+ def with_default_modules
27
+ modules << Format
28
+ modules << GoodCase
29
+ modules << Typo
30
+ modules << UnusedFields
31
+ self
32
+ end
33
+
34
+ def with_all_modules
35
+ modules << Format
36
+ modules << ExtraVerbs
37
+ modules << GoodCase
38
+ modules << Typo
39
+ modules << UnusedFields
40
+ self
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiTester
4
+ # Holds data necessary for tests
5
+ class BoundaryCase
6
+ attr_accessor :payload
7
+ attr_accessor :headers
8
+ attr_accessor :description
9
+
10
+ def initialize(description:, payload:, headers:)
11
+ self.description = description
12
+ self.payload = payload
13
+ self.headers = headers
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiTester
4
+ # Class to define the whole contract
5
+ class Contract
6
+ attr_accessor :name
7
+ attr_accessor :endpoints
8
+ attr_accessor :base_url
9
+
10
+ def initialize(name:, base_url:)
11
+ self.name = name
12
+ self.endpoints = []
13
+ self.base_url = base_url
14
+ end
15
+
16
+ def add_endpoint(endpoint)
17
+ endpoints << endpoint
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/response'
4
+ require 'api-tester/definition/method'
5
+ require 'api-tester/test_helper'
6
+ require 'rest-client'
7
+ require 'json'
8
+
9
+ module ApiTester
10
+ # Class for defining and interacting with endpoints in a contract
11
+ class Endpoint
12
+ attr_accessor :name
13
+ attr_accessor :relative_url
14
+ attr_accessor :path_params
15
+ attr_accessor :methods
16
+ attr_accessor :test_helper
17
+ attr_accessor :bad_request_response
18
+ attr_accessor :not_allowed_response
19
+ attr_accessor :not_found_response
20
+
21
+ def initialize(name:, relative_url:)
22
+ self.relative_url = relative_url
23
+ self.name = name
24
+ self.methods = []
25
+ self.path_params = []
26
+ self.test_helper = ApiTester::TestHelper.new
27
+ self.bad_request_response = ApiTester::Response.new status_code: 400
28
+ self.not_allowed_response = ApiTester::Response.new status_code: 415
29
+ self.not_found_response = ApiTester::Response.new status_code: 404
30
+ end
31
+
32
+ def url
33
+ temp_url = relative_url
34
+ path_params.each do |param|
35
+ temp_url.sub! "{#{param}}", test_helper.retrieve_param(param)
36
+ end
37
+ temp_url
38
+ end
39
+
40
+ def default_call(base_url)
41
+ test_helper.before
42
+ method_defaults = methods[0].default_request
43
+ method_defaults[:url] = "#{base_url}#{url}"
44
+ begin
45
+ response = RestClient::Request.execute(method_defaults)
46
+ rescue RestClient::ExceptionWithResponse => e
47
+ response = e.response
48
+ end
49
+ test_helper.after
50
+ response
51
+ end
52
+
53
+ def call(base_url:, method:, query: '', payload: {}, headers: {})
54
+ test_helper.before
55
+ url = query ? "#{base_url}#{self.url}?#{query}" : "#{base_url}#{self.url}"
56
+ begin
57
+ response = RestClient::Request.execute(method: method.verb,
58
+ url: url,
59
+ payload: payload.to_json,
60
+ headers: headers)
61
+ rescue RestClient::ExceptionWithResponse => e
62
+ response = e.response
63
+ end
64
+ test_helper.after
65
+ response
66
+ end
67
+
68
+ def add_method(verb:, response:, request: Request.new)
69
+ methods << ApiTester::Method.new(verb: verb,
70
+ response: response,
71
+ request: request)
72
+ self
73
+ end
74
+
75
+ def add_path_param(param)
76
+ path_params << param
77
+ self
78
+ end
79
+
80
+ def verbs
81
+ methods.map(&:verb)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/fields/field'
4
+
5
+ module ApiTester
6
+ # Class used for defining array fields
7
+ class ArrayField < 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
+ return [] if fields.size.zero?
26
+
27
+ obj = {}
28
+ fields.each do |field|
29
+ obj[field.name] = field.default_value
30
+ end
31
+ [obj]
32
+ end
33
+
34
+ def negative_boundary_values
35
+ super +
36
+ [
37
+ 'string',
38
+ 123,
39
+ 0,
40
+ 1,
41
+ true,
42
+ false,
43
+ {}
44
+ ]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/fields/field'
4
+
5
+ module ApiTester
6
+ # Class for defining booleans in contract
7
+ class BooleanField < Field
8
+ def initialize(name:, default_value: true, 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
+ ]
21
+ end
22
+ end
23
+ 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 email fields in contract
7
+ class EmailField < Field
8
+ def initialize(name:, default_value: 'test@test.com', 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
+ 1,
18
+ 0,
19
+ true,
20
+ false,
21
+ {}
22
+ ]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api-tester/definition/fields/field'
4
+
5
+ module ApiTester
6
+ # Class for defining enumerators
7
+ class EnumField < Field
8
+ attr_accessor :acceptable_values
9
+
10
+ def initialize(name:, acceptable_values:, default_value: nil, required: false)
11
+ if default_value
12
+ super name: name, default_value: default_value, required: required
13
+ else
14
+ super name: name, default_value: acceptable_values[0], required: required
15
+ end
16
+
17
+ self.acceptable_values = acceptable_values
18
+ end
19
+
20
+ def negative_boundary_values
21
+ super +
22
+ [
23
+ 123,
24
+ 0,
25
+ 1,
26
+ true,
27
+ false,
28
+ {}
29
+ ]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiTester
4
+ # Base class for field definitions
5
+ class Field
6
+ attr_accessor :name
7
+ attr_accessor :default_value
8
+ attr_accessor :required
9
+ attr_accessor :is_seen
10
+
11
+ def initialize(name:, required: false, default_value: 'string')
12
+ self.name = name
13
+ self.default_value = default_value
14
+ self.required = required
15
+ self.is_seen = 0
16
+ end
17
+
18
+ def is_required
19
+ self.required = true
20
+ self
21
+ end
22
+
23
+ def is_not_required
24
+ self.required = false
25
+ self
26
+ end
27
+
28
+ def subfields?
29
+ false
30
+ end
31
+
32
+ def fields
33
+ []
34
+ end
35
+
36
+ def negative_boundary_values
37
+ cases = []
38
+ cases << nil if required
39
+ cases
40
+ end
41
+
42
+ def seen
43
+ self.is_seen += 1
44
+ end
45
+
46
+ def display_class
47
+ self.class
48
+ end
49
+ end
50
+ end