api-tester 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) 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/.rubocop.yml +61 -0
  6. data/Gemfile +2 -0
  7. data/Guardfile +70 -0
  8. data/README.md +65 -61
  9. data/Rakefile +8 -3
  10. data/api-tester.gemspec +29 -24
  11. data/changelog.txt +10 -0
  12. data/lib/api-tester.rb +6 -3
  13. data/lib/api-tester/config.rb +16 -13
  14. data/lib/api-tester/definition/boundary_case.rb +4 -1
  15. data/lib/api-tester/definition/contract.rb +8 -3
  16. data/lib/api-tester/definition/endpoint.rb +32 -23
  17. data/lib/api-tester/definition/fields/array_field.rb +20 -19
  18. data/lib/api-tester/definition/fields/boolean_field.rb +12 -9
  19. data/lib/api-tester/definition/fields/email_field.rb +14 -11
  20. data/lib/api-tester/definition/fields/enum_field.rb +14 -11
  21. data/lib/api-tester/definition/fields/field.rb +46 -45
  22. data/lib/api-tester/definition/fields/number_field.rb +11 -8
  23. data/lib/api-tester/definition/fields/object_field.rb +34 -31
  24. data/lib/api-tester/definition/fields/plain_array_field.rb +25 -0
  25. data/lib/api-tester/definition/method.rb +7 -2
  26. data/lib/api-tester/definition/request.rb +43 -16
  27. data/lib/api-tester/definition/response.rb +29 -26
  28. data/lib/api-tester/method_case_test.rb +67 -53
  29. data/lib/api-tester/modules/extra_verbs.rb +29 -9
  30. data/lib/api-tester/modules/format.rb +23 -7
  31. data/lib/api-tester/modules/good_case.rb +25 -10
  32. data/lib/api-tester/modules/injection_module.rb +32 -17
  33. data/lib/api-tester/modules/required_fields.rb +51 -0
  34. data/lib/api-tester/modules/server_information.rb +13 -10
  35. data/lib/api-tester/modules/typo.rb +36 -13
  36. data/lib/api-tester/modules/unexpected_fields.rb +61 -0
  37. data/lib/api-tester/modules/unused_fields.rb +12 -6
  38. data/lib/api-tester/reporter/api_report.rb +24 -16
  39. data/lib/api-tester/reporter/missing_field_report.rb +12 -13
  40. data/lib/api-tester/reporter/report.rb +11 -8
  41. data/lib/api-tester/reporter/status_code_report.rb +9 -2
  42. data/lib/api-tester/test_helper.rb +6 -6
  43. data/lib/api-tester/util/response_evaluator.rb +70 -57
  44. data/lib/api-tester/util/supported_verbs.rb +8 -5
  45. data/lib/api-tester/version.rb +3 -1
  46. metadata +99 -25
  47. data/.travis.yml +0 -6
  48. data/lib/api-tester/reporter/missing_response_field_report.rb +0 -21
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]
@@ -4,39 +4,44 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'api-tester/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "api-tester"
7
+ spec.name = 'api-tester'
8
8
  spec.version = ApiTester::VERSION
9
- spec.authors = ["arane"]
10
- spec.email = ["arane9@gmail.com"]
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"
37
- spec.add_development_dependency "pry", "~> 0.11"
38
- spec.add_development_dependency "require_all", "~>2.0.0"
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'
39
44
 
40
- spec.add_runtime_dependency "rest-client", "~> 2.0"
41
- spec.add_runtime_dependency "injection_vulnerability_library", "0.0.2"
45
+ spec.add_runtime_dependency 'injection_vulnerability_library', '0.0.2'
46
+ spec.add_runtime_dependency 'rest-client', '~> 2.0'
42
47
  end
@@ -1,3 +1,13 @@
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
+
1
11
  1.0.0
2
12
 
3
13
  - Switching to using named variables to make future changes less breaky
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Tool for testing through API definitions
1
4
  module ApiTester
2
- def self.go contract, config
5
+ def self.go(contract, config)
3
6
  reporter = config.reporter
4
7
 
5
- config.modules.sort_by{ |mod| mod.order }.each do |mod|
8
+ config.modules.sort_by(&:order).each do |mod|
6
9
  reporter.add_reports mod.go contract
7
10
  end
8
11
 
9
12
  reporter.print
10
- reporter.reports.size == 0
13
+ reporter.reports.size.zero?
11
14
  end
12
15
  end
@@ -1,39 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'api-tester/reporter/api_report'
2
4
 
3
5
  module ApiTester
6
+ # Config class for changing how the tool operates
4
7
  class Config
5
8
  attr_accessor :reporter
6
9
  attr_accessor :modules
7
10
 
8
- def initialize reporter=ApiTester::ApiReport.new
11
+ def initialize(reporter: ApiTester::ApiReport.new)
9
12
  self.reporter = reporter
10
13
  self.modules = []
11
14
  end
12
15
 
13
- def with_reporter reporter
16
+ def with_reporter(reporter)
14
17
  self.reporter = reporter
15
18
  self
16
19
  end
17
20
 
18
- def with_module new_module
19
- self.modules << new_module
21
+ def with_module(new_module)
22
+ modules << new_module
20
23
  self
21
24
  end
22
25
 
23
26
  def with_default_modules
24
- self.modules << Format
25
- self.modules << GoodCase
26
- self.modules << Typo
27
- self.modules << UnusedFields
27
+ modules << Format
28
+ modules << GoodCase
29
+ modules << Typo
30
+ modules << UnusedFields
28
31
  self
29
32
  end
30
33
 
31
34
  def with_all_modules
32
- self.modules << Format
33
- self.modules << ExtraVerbs
34
- self.modules << GoodCase
35
- self.modules << Typo
36
- self.modules << UnusedFields
35
+ modules << Format
36
+ modules << ExtraVerbs
37
+ modules << GoodCase
38
+ modules << Typo
39
+ modules << UnusedFields
37
40
  self
38
41
  end
39
42
  end
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiTester
4
+ # Holds data necessary for tests
2
5
  class BoundaryCase
3
6
  attr_accessor :payload
4
7
  attr_accessor :headers
5
8
  attr_accessor :description
6
9
 
7
- def initialize description, payload, headers
10
+ def initialize(description:, payload:, headers:)
8
11
  self.description = description
9
12
  self.payload = payload
10
13
  self.headers = headers
@@ -1,15 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiTester
4
+ # Class to define the whole contract
2
5
  class Contract
3
6
  attr_accessor :name
4
7
  attr_accessor :endpoints
8
+ attr_accessor :base_url
5
9
 
6
- def initialize name
10
+ def initialize(name:, base_url:)
7
11
  self.name = name
8
12
  self.endpoints = []
13
+ self.base_url = base_url
9
14
  end
10
15
 
11
- def add_endpoint endpoint
12
- self.endpoints << endpoint
16
+ def add_endpoint(endpoint)
17
+ endpoints << endpoint
13
18
  end
14
19
  end
15
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'api-tester/definition/response'
2
4
  require 'api-tester/definition/method'
3
5
  require 'api-tester/test_helper'
@@ -5,9 +7,10 @@ require 'rest-client'
5
7
  require 'json'
6
8
 
7
9
  module ApiTester
10
+ # Class for defining and interacting with endpoints in a contract
8
11
  class Endpoint
9
12
  attr_accessor :name
10
- attr_accessor :base_url
13
+ attr_accessor :relative_url
11
14
  attr_accessor :path_params
12
15
  attr_accessor :methods
13
16
  attr_accessor :test_helper
@@ -15,61 +18,67 @@ module ApiTester
15
18
  attr_accessor :not_allowed_response
16
19
  attr_accessor :not_found_response
17
20
 
18
- def initialize name, url
19
- self.base_url = url
21
+ def initialize(name:, relative_url:)
22
+ self.relative_url = relative_url
20
23
  self.name = name
21
24
  self.methods = []
22
25
  self.path_params = []
23
26
  self.test_helper = ApiTester::TestHelper.new
24
- self.bad_request_response = ApiTester::Response.new 400
25
- self.not_allowed_response = ApiTester::Response.new 415
26
- self.not_found_response = ApiTester::Response.new 404
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
27
30
  end
28
31
 
29
32
  def url
30
- temp_url = self.base_url
31
- self.path_params.each do |param|
32
- temp_url.sub! "{#{param}}", self.test_helper.retrieve_param(param)
33
+ temp_url = relative_url
34
+ path_params.each do |param|
35
+ temp_url.sub! "{#{param}}", test_helper.retrieve_param(param)
33
36
  end
34
37
  temp_url
35
38
  end
36
39
 
37
- def default_call
38
- self.test_helper.before
39
- method_defaults = self.methods[0].default_request
40
- method_defaults[:url] = self.url
40
+ def default_call(base_url)
41
+ test_helper.before
42
+ method_defaults = methods[0].default_request
43
+ method_defaults[:url] = "#{base_url}#{url}"
41
44
  begin
42
45
  response = RestClient::Request.execute(method_defaults)
43
46
  rescue RestClient::ExceptionWithResponse => e
44
47
  response = e.response
45
48
  end
46
- self.test_helper.after
49
+ test_helper.after
47
50
  response
48
51
  end
49
52
 
50
- def call method, payload={}, headers={}
51
- self.test_helper.before
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}"
52
56
  begin
53
- response = RestClient::Request.execute(method: method.verb, url: self.url, payload: payload.to_json, headers: headers)
57
+ response = RestClient::Request.execute(method: method.verb,
58
+ url: url,
59
+ payload: payload.to_json,
60
+ headers: headers)
54
61
  rescue RestClient::ExceptionWithResponse => e
55
62
  response = e.response
56
63
  end
57
- self.test_helper.after
64
+ test_helper.after
58
65
  response
59
66
  end
60
67
 
61
- def add_method verb, response, request=Request.new()
62
- self.methods << ApiTester::Method.new(verb, response, request)
68
+ def add_method(verb:, response:, request: Request.new)
69
+ methods << ApiTester::Method.new(verb: verb,
70
+ response: response,
71
+ request: request)
63
72
  self
64
73
  end
65
74
 
66
- def add_path_param param
67
- self.path_params << param
75
+ def add_path_param(param)
76
+ path_params << param
68
77
  self
69
78
  end
70
79
 
71
80
  def verbs
72
- self.methods.map(&:verb)
81
+ methods.map(&:verb)
73
82
  end
74
83
  end
75
84
  end
@@ -1,30 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'api-tester/definition/fields/field'
2
4
 
3
5
  module ApiTester
6
+ # Class used for defining array fields
4
7
  class ArrayField < Field
5
8
  attr_accessor :fields
6
9
 
7
- def initialize name:
8
- super name: name
10
+ def initialize(name:, required: false)
11
+ super name: name, required: required
9
12
  self.fields = []
10
13
  end
11
14
 
12
- def with_field newField
13
- self.fields << newField
15
+ def with_field(new_field)
16
+ fields << new_field
14
17
  self
15
18
  end
16
19
 
17
- def has_subfields?
20
+ def subfields?
18
21
  true
19
22
  end
20
23
 
21
24
  def default_value
22
- if self.fields.size == 0
23
- return []
24
- end
25
+ return [] if fields.size.zero?
25
26
 
26
- obj = Hash.new
27
- self.fields.each do |field|
27
+ obj = {}
28
+ fields.each do |field|
28
29
  obj[field.name] = field.default_value
29
30
  end
30
31
  [obj]
@@ -32,15 +33,15 @@ module ApiTester
32
33
 
33
34
  def negative_boundary_values
34
35
  super +
35
- [
36
- "string",
37
- 123,
38
- 0,
39
- 1,
40
- true,
41
- false,
42
- {}
43
- ]
36
+ [
37
+ 'string',
38
+ 123,
39
+ 0,
40
+ 1,
41
+ true,
42
+ false,
43
+ {}
44
+ ]
44
45
  end
45
46
  end
46
47
  end
@@ -1,20 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'api-tester/definition/fields/field'
2
4
 
3
5
  module ApiTester
6
+ # Class for defining booleans in contract
4
7
  class BooleanField < Field
5
- def initialize name:, default_value: true
6
- super name: name, default_value: default_value
8
+ def initialize(name:, default_value: true, required: false)
9
+ super name: name, default_value: default_value, required: required
7
10
  end
8
11
 
9
12
  def negative_boundary_values
10
13
  super +
11
- [
12
- "string",
13
- 123,
14
- 0,
15
- 1,
16
- {}
17
- ]
14
+ [
15
+ 'string',
16
+ 123,
17
+ 0,
18
+ 1,
19
+ {}
20
+ ]
18
21
  end
19
22
  end
20
23
  end
@@ -1,22 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'api-tester/definition/fields/field'
2
4
 
3
5
  module ApiTester
6
+ # Class for defining email fields in contract
4
7
  class EmailField < Field
5
- def initialize name:, default_value: "test@test.com"
6
- super name: name, default_value: default_value
8
+ def initialize(name:, default_value: 'test@test.com', required: false)
9
+ super name: name, default_value: default_value, required: required
7
10
  end
8
11
 
9
12
  def negative_boundary_values
10
13
  super +
11
- [
12
- "string",
13
- 123,
14
- 1,
15
- 0,
16
- true,
17
- false,
18
- {}
19
- ]
14
+ [
15
+ 'string',
16
+ 123,
17
+ 1,
18
+ 0,
19
+ true,
20
+ false,
21
+ {}
22
+ ]
20
23
  end
21
24
  end
22
25
  end