api-tester 1.0.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 (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