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.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +15 -0
- data/.github/workflows/push.yml +39 -0
- data/.github/workflows/test.yml +31 -0
- data/.rspec +1 -0
- data/.rubocop.yml +61 -0
- data/Gemfile +2 -0
- data/Guardfile +70 -0
- data/README.md +106 -74
- data/Rakefile +8 -3
- data/api-tester.gemspec +31 -23
- data/changelog.txt +35 -0
- data/lib/api-tester.rb +15 -0
- data/lib/api-tester/config.rb +43 -0
- data/lib/api-tester/definition/boundary_case.rb +16 -0
- data/lib/api-tester/definition/contract.rb +20 -0
- data/lib/api-tester/definition/endpoint.rb +84 -0
- data/lib/api-tester/definition/fields/array_field.rb +47 -0
- data/lib/api-tester/definition/fields/boolean_field.rb +23 -0
- data/lib/api-tester/definition/fields/email_field.rb +25 -0
- data/lib/api-tester/definition/fields/enum_field.rb +32 -0
- data/lib/api-tester/definition/fields/field.rb +50 -0
- data/lib/api-tester/definition/fields/number_field.rb +22 -0
- data/lib/api-tester/definition/fields/object_field.rb +47 -0
- data/lib/api-tester/definition/fields/plain_array_field.rb +25 -0
- data/lib/api-tester/definition/method.rb +22 -0
- data/lib/api-tester/definition/request.rb +96 -0
- data/lib/api-tester/definition/response.rb +39 -0
- data/lib/api-tester/method_case_test.rb +83 -0
- data/lib/api-tester/modules/extra_verbs.rb +53 -0
- data/lib/api-tester/modules/format.rb +47 -0
- data/lib/api-tester/modules/good_case.rb +46 -0
- data/lib/api-tester/modules/injection_module.rb +81 -0
- data/lib/api-tester/modules/required_fields.rb +51 -0
- data/lib/api-tester/modules/server_information.rb +42 -0
- data/lib/api-tester/modules/typo.rb +70 -0
- data/lib/api-tester/modules/unexpected_fields.rb +61 -0
- data/lib/api-tester/modules/unused_fields.rb +31 -0
- data/lib/api-tester/reporter/api_report.rb +47 -0
- data/lib/api-tester/reporter/missing_field_report.rb +24 -0
- data/lib/api-tester/reporter/report.rb +30 -0
- data/lib/api-tester/reporter/status_code_report.rb +21 -0
- data/lib/api-tester/test_helper.rb +12 -0
- data/lib/api-tester/util/response_evaluator.rb +88 -0
- data/lib/api-tester/util/supported_verbs.rb +39 -0
- data/lib/api-tester/version.rb +5 -0
- metadata +159 -42
- data/.travis.yml +0 -6
- data/lib/tester.rb +0 -7
- data/lib/tester/api_tester.rb +0 -50
- data/lib/tester/definition/api_contract.rb +0 -13
- data/lib/tester/definition/api_method.rb +0 -11
- data/lib/tester/definition/boundary_case.rb +0 -11
- data/lib/tester/definition/endpoint.rb +0 -57
- data/lib/tester/definition/fields/array_field.rb +0 -44
- data/lib/tester/definition/fields/boolean_field.rb +0 -18
- data/lib/tester/definition/fields/email_field.rb +0 -20
- data/lib/tester/definition/fields/enum_field.rb +0 -27
- data/lib/tester/definition/fields/field.rb +0 -47
- data/lib/tester/definition/fields/number_field.rb +0 -17
- data/lib/tester/definition/fields/object_field.rb +0 -42
- data/lib/tester/definition/request.rb +0 -49
- data/lib/tester/definition/response.rb +0 -34
- data/lib/tester/method_case_test.rb +0 -67
- data/lib/tester/modules/extra_verbs.rb +0 -25
- data/lib/tester/modules/format.rb +0 -26
- data/lib/tester/modules/good_case.rb +0 -29
- data/lib/tester/modules/module.rb +0 -18
- data/lib/tester/modules/typo.rb +0 -41
- data/lib/tester/modules/unused_fields.rb +0 -22
- data/lib/tester/reporter/api_report.rb +0 -33
- data/lib/tester/reporter/missing_field_report.rb +0 -23
- data/lib/tester/reporter/missing_response_field_report.rb +0 -19
- data/lib/tester/reporter/report.rb +0 -25
- data/lib/tester/reporter/status_code_report.rb +0 -12
- data/lib/tester/test_helper.rb +0 -10
- data/lib/tester/util/response_evaluator.rb +0 -73
- data/lib/tester/util/supported_verbs.rb +0 -34
- data/lib/tester/version.rb +0 -3
data/Rakefile
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
require
|
2
|
-
require
|
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]
|
data/api-tester.gemspec
CHANGED
@@ -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 =
|
8
|
-
spec.version =
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
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 =
|
13
|
-
spec.description =
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
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
|
23
|
-
|
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
|
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 =
|
29
|
+
spec.bindir = 'exe'
|
30
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = [
|
31
|
+
spec.require_paths = ['lib']
|
32
32
|
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
35
|
-
spec.add_development_dependency
|
36
|
-
spec.add_development_dependency
|
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
|
45
|
+
spec.add_runtime_dependency 'injection_vulnerability_library', '0.0.2'
|
46
|
+
spec.add_runtime_dependency 'rest-client', '~> 2.0'
|
39
47
|
end
|
data/changelog.txt
CHANGED
@@ -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
|
data/lib/api-tester.rb
ADDED
@@ -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
|