apivore 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e65e5c0dae07a51ff67726b32b3e6a244d3f4a88
4
- data.tar.gz: bd8bf1e39471e1671044321177cb12230a0a04e1
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWNjOGI2MTJjZjgyM2M5MjRiYjA1ODg4YTA1MmRkOWY0YTlkYzlmNw==
5
+ data.tar.gz: !binary |-
6
+ N2JjZWIzYTlhN2UyNTNhNjJkMDZiYjIxNTA4NWYzY2FiNGYxM2RjYw==
5
7
  SHA512:
6
- metadata.gz: 9b72501bc2be6fb51da035c5527ed4d9c0b2518820e4801ad8b53e9c111aa0aee5ef8d3a3ecd456a7a5db88968536385bdbeba19c88174886f138e143a9a4a5e
7
- data.tar.gz: f6b065b7d68128e4d36206555c5b5d96864ea85adf9163775bb00c1d40f7fcf1bc8362908c1e72dfe3ad3996a170e408e0d503b6079ceef1d16138f431574b97
8
+ metadata.gz: !binary |-
9
+ Y2NhZDZiYTdmNWNlYzYwMWRkZTVlMDkyZmQwMWI4OTdkNGE1NTU0YjY5Mzgy
10
+ ZGU4NWI3MjA4YjBjMWYwZWM3MDc0Mjk2ODc5YjRhODhhMmQxYzg0ODJjMWFl
11
+ Zjk4YzdhMTljMzI2YTRjNWMwMGNlY2RhNmM5NTZhNDlhNWI2YzA=
12
+ data.tar.gz: !binary |-
13
+ NDI1NjQyODRlYzZhZjFjN2UxODZmZGMzN2Y4MjBmMDc3NjI0Yzc2OTNhNTBm
14
+ M2Y1YjUxZGY0OWRmZGFiZTc0ZTcwZjVkZTFjOTI0MzU2YTgyMDEwYzA5YTY4
15
+ YzNkZmY1NDQ2MDE5Y2UxNDM2NGY3MzAyMzI4NWRiZDRkNDM2Yjc=
@@ -1,48 +1,10 @@
1
- require 'apivore/rspec_builder'
2
1
  require 'apivore/rspec_matchers'
3
-
4
- module Apivore
5
- class Swagger < Hashie::Mash
6
-
7
- def validate
8
- case version
9
- when '2.0'
10
- schema = File.read(File.expand_path("../../data/swagger_2.0_schema.json", __FILE__))
11
- else
12
- raise "Unknown/unsupported Swagger version to validate against: #{version}"
13
- end
14
- JSON::Validator.fully_validate(schema, self)
15
- end
16
-
17
- def version
18
- swagger
19
- end
20
-
21
- def base_path
22
- self['basePath'] || ''
23
- end
24
-
25
- def each_response(&block)
26
- paths.each do |path, path_data|
27
- path_data.each do |verb, method_data|
28
- raise "No responses found in swagger for path '#{path}', method #{verb}: #{method_data.inspect}" if method_data.responses.nil?
29
- method_data.responses.each do |response_code, response_data|
30
- schema_location = nil
31
- if response_data.schema
32
- schema_location = Fragment.new ['#', 'paths', path, verb, 'responses', response_code, 'schema']
33
- end
34
- block.call(path, verb, response_code, schema_location)
35
- end
36
- end
37
- end
38
- end
39
- end
40
-
41
- # This is a workaround for json-schema's fragment validation which does not allow paths to contain forward slashes
42
- # current json-schema attempts to split('/') on a string path to produce an array.
43
- class Fragment < Array
44
- def split(options = nil)
45
- self
46
- end
47
- end
2
+ require 'apivore/rspec_helpers'
3
+ require 'apivore/swagger_checker'
4
+ require 'apivore/swagger'
5
+ require 'rspec'
6
+
7
+ RSpec.configure do |config|
8
+ config.include Apivore::RspecMatchers, type: :apivore
9
+ config.include Apivore::RspecHelpers, type: :apivore
48
10
  end
@@ -0,0 +1,25 @@
1
+ module Apivore
2
+ class AllRoutesTestedValidator
3
+
4
+ def matches?(swagger_checker)
5
+ @errors = []
6
+ swagger_checker.mappings.each do |path, methods|
7
+ methods.each do |method, codes|
8
+ codes.each do |code, _|
9
+ @errors << "#{method} #{path} is untested for response code #{code}"
10
+ end
11
+ end
12
+ end
13
+
14
+ @errors.empty?
15
+ end
16
+
17
+ def description
18
+ "have tested all documented routes"
19
+ end
20
+
21
+ def failure_message
22
+ @errors.join("\n")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Apivore
2
+ # This is a workaround for json-schema's fragment validation which does not allow paths to contain forward slashes
3
+ # current json-schema attempts to split('/') on a string path to produce an array.
4
+ class Fragment < Array
5
+ def split(options = nil)
6
+ self
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ require 'apivore/validator'
2
+ require 'apivore/all_routes_tested_validator'
3
+
4
+ module Apivore
5
+ module RspecHelpers
6
+ def validate(method, path, response_code, params = {})
7
+ Validator.new(method, path, response_code, params)
8
+ end
9
+
10
+ def validate_all_paths
11
+ AllRoutesTestedValidator.new
12
+ end
13
+ end
14
+ end
@@ -5,41 +5,11 @@ require 'net/http'
5
5
  module Apivore
6
6
  module RspecMatchers
7
7
  extend RSpec::Matchers::DSL
8
- matcher :be_valid_swagger do |version|
9
- match do |body|
10
- @api_description = Swagger.new(JSON.parse(body))
11
- @errors = @api_description.validate
12
- @errors.empty?
13
- end
14
-
15
- failure_message do |body|
16
- msg = "The document fails to validate as Swagger #{@api_description.version}:\n"
17
- msg += @errors.join("\n")
18
- end
19
- end
20
-
21
- matcher :have_models_for_all_get_endpoints do
22
- match do |body|
23
- @errors = []
24
- swagger = Swagger.new(JSON.parse(body))
25
- swagger.each_response do |path, method, response_code, schema|
26
- if method == 'get' && !schema
27
- @errors << "Unable to find a valid model for #{path} get #{response_code} response."
28
- end
29
- end
30
- @errors.empty?
31
- end
32
-
33
- failure_message do
34
- @errors.join("\n")
35
- end
36
- end
37
-
38
- matcher :be_consistent_with_swagger_definitions do |master_swagger, current_service|
8
+ matcher :be_consistent_with_swagger_definitions do |master_swagger_host, current_service|
39
9
 
40
10
  attr_reader :actual, :expected
41
11
 
42
- def cleaned_definitions(definitions, current_service)
12
+ define_method :cleaned_definitions do |definitions, current_service|
43
13
  definitions.each do |key, definition_fields|
44
14
  # We ignore definitions that are owned exclusively by the current_service
45
15
  if [current_service] == definition_fields['x-services']
@@ -52,8 +22,17 @@ module Apivore
52
22
  end.select{ |_, value| !value.nil? }
53
23
  end
54
24
 
55
- match do |body|
56
- our_swagger = JSON.parse(body)
25
+ define_method :fetch_master_swagger do
26
+ req = Net::HTTP.get(master_swagger_host, "/swagger.json")
27
+ JSON.parse(req)
28
+ end
29
+
30
+ define_method :master_swagger do
31
+ @master_swagger ||= fetch_master_swagger
32
+ end
33
+
34
+ match do |swagger_checker|
35
+ our_swagger = swagger_checker.swagger
57
36
  master_definitions = cleaned_definitions(master_swagger["definitions"], current_service)
58
37
  our_definitions = our_swagger["definitions"]
59
38
  @actual = our_definitions.slice(*master_definitions.keys)
@@ -63,17 +42,5 @@ module Apivore
63
42
 
64
43
  diffable
65
44
  end
66
-
67
- matcher :conform_to_the_documented_model_for do |swagger, fragment|
68
- match do |body|
69
- body = JSON.parse(body)
70
- @errors = JSON::Validator.fully_validate(swagger, body, fragment: fragment)
71
- @errors.empty?
72
- end
73
-
74
- failure_message do |body|
75
- @errors.map { |e| e.sub("'#", "'#{path}#").gsub(/^The property|in schema.*$/,'') }.join("\n")
76
- end
77
- end
78
45
  end
79
46
  end
@@ -0,0 +1,41 @@
1
+ require 'hashie'
2
+
3
+ require 'apivore/fragment'
4
+
5
+ module Apivore
6
+ class Swagger < Hashie::Mash
7
+
8
+ def validate
9
+ case version
10
+ when '2.0'
11
+ schema = File.read(File.expand_path("../../../data/swagger_2.0_schema.json", __FILE__))
12
+ else
13
+ raise "Unknown/unsupported Swagger version to validate against: #{version}"
14
+ end
15
+ JSON::Validator.fully_validate(schema, self)
16
+ end
17
+
18
+ def version
19
+ swagger
20
+ end
21
+
22
+ def base_path
23
+ self['basePath'] || ''
24
+ end
25
+
26
+ def each_response(&block)
27
+ paths.each do |path, path_data|
28
+ path_data.each do |verb, method_data|
29
+ raise "No responses found in swagger for path '#{path}', method #{verb}: #{method_data.inspect}" if method_data.responses.nil?
30
+ method_data.responses.each do |response_code, response_data|
31
+ schema_location = nil
32
+ if response_data.schema
33
+ schema_location = Fragment.new ['#', 'paths', path, verb, 'responses', response_code, 'schema']
34
+ end
35
+ block.call(path, verb, response_code, schema_location)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ module Apivore
2
+ class SwaggerChecker
3
+ PATH_TO_CHECKER_MAP = {}
4
+
5
+ def self.instance_for(path)
6
+ PATH_TO_CHECKER_MAP[path] ||= new(path)
7
+ end
8
+
9
+ def has_path?(path)
10
+ mappings.has_key?(path)
11
+ end
12
+
13
+ def has_method_at_path?(path, method)
14
+ mappings[path].has_key?(method)
15
+ end
16
+
17
+ def has_response_code_for_path?(path, method, code)
18
+ mappings[path][method].has_key?(code.to_s)
19
+ end
20
+
21
+ def has_matching_document_for(path, method, code, body)
22
+ JSON::Validator.fully_validate(
23
+ swagger, body, fragment: fragment(path, method, code)
24
+ )
25
+ end
26
+
27
+ def fragment(path, method, code)
28
+ mappings[path][method.to_s][code.to_s]
29
+ end
30
+
31
+ def remove_tested_end_point_response(path, method, code)
32
+ mappings[path][method].delete(code.to_s)
33
+ if mappings[path][method].size == 0
34
+ mappings[path].delete(method)
35
+ if mappings[path].size == 0
36
+ mappings.delete(path)
37
+ end
38
+ end
39
+ end
40
+
41
+ def base_path
42
+ @swagger.base_path
43
+ end
44
+
45
+ attr_reader :swagger_path, :mappings, :swagger
46
+
47
+ private
48
+
49
+ def initialize(swagger_path)
50
+ @swagger_path = swagger_path
51
+ load_swagger_doc!
52
+ validate_swagger!
53
+ setup_mappings!
54
+ end
55
+
56
+ def load_swagger_doc!
57
+ @swagger = Apivore::Swagger.new(fetch_swagger!)
58
+ end
59
+
60
+ def fetch_swagger!
61
+ session = ActionDispatch::Integration::Session.new(Rails.application)
62
+ begin
63
+ session.get(swagger_path)
64
+ rescue
65
+ fail "Unable to perform GET request for swagger json: #{swagger_path} - #{$!}."
66
+ end
67
+ JSON.parse(session.response.body)
68
+ end
69
+
70
+ def validate_swagger!
71
+ errors = swagger.validate
72
+ unless errors.empty?
73
+ msg = "The document fails to validate as Swagger #{swagger.version}:\n"
74
+ msg += errors.join("\n")
75
+ fail msg
76
+ end
77
+ end
78
+
79
+ def setup_mappings!
80
+ @mappings = {}
81
+ @swagger.each_response do |path, method, response_code, fragment|
82
+ @mappings[path] ||= {}
83
+ @mappings[path][method] ||= {}
84
+ raise "duplicate" unless @mappings[path][method][response_code].nil?
85
+ @mappings[path][method][response_code] = fragment
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,132 @@
1
+ require 'action_controller'
2
+ require 'action_dispatch'
3
+
4
+ module Apivore
5
+ class Validator
6
+ include ::ActionDispatch::Integration::Runner
7
+
8
+ attr_reader :method, :path, :expected_response_code, :params
9
+
10
+ def initialize(method, path, expected_response_code, params = {})
11
+ @method = method.to_s
12
+ @path = path.to_s
13
+ @params = params
14
+ @expected_response_code = expected_response_code.to_i
15
+ end
16
+
17
+ def matches?(swagger_checker)
18
+ pre_checks(swagger_checker)
19
+
20
+ unless has_errors?
21
+ send(
22
+ method,
23
+ full_path(swagger_checker),
24
+ params['_data'] || {},
25
+ params['_headers'] || {}
26
+ )
27
+ post_checks(swagger_checker)
28
+ swagger_checker.remove_tested_end_point_response(
29
+ path, method, expected_response_code
30
+ )
31
+ end
32
+
33
+ !has_errors?
34
+ end
35
+
36
+ def full_path(swagger_checker)
37
+ apivore_build_path(swagger_checker.base_path + path, params)
38
+ end
39
+
40
+ def apivore_build_path(path, data)
41
+ path.scan(/\{([^\}]*)\}/).each do |param|
42
+ key = param.first
43
+ if data && data[key]
44
+ path = path.gsub "{#{key}}", data[key].to_s
45
+ else
46
+ raise URI::InvalidURIError, "No substitution data found for {#{key}}"\
47
+ " to test the path #{path}.", caller
48
+ end
49
+ end
50
+ path + (data['_query_string'] ? "?#{data['_query_string']}" : '')
51
+ end
52
+
53
+
54
+ def pre_checks(swagger_checker)
55
+ check_request_path(swagger_checker)
56
+ end
57
+
58
+ def post_checks(swagger_checker)
59
+ check_status_code
60
+ check_response_is_valid(swagger_checker) unless has_errors?
61
+ end
62
+
63
+ def check_request_path(swagger_checker)
64
+ if !swagger_checker.has_path?(path)
65
+ errors << "Swagger doc: #{swagger_checker.swagger_path} does not have"\
66
+ " a documented path for #{path}"
67
+ elsif !swagger_checker.has_method_at_path?(path, method)
68
+ errors << "Swagger doc: #{swagger_checker.swagger_path} does not have"\
69
+ " a documented path for #{method} #{path}"
70
+ elsif !swagger_checker.has_response_code_for_path?(path, method, expected_response_code)
71
+ errors << "Swagger doc: #{swagger_checker.swagger_path} does not have"\
72
+ " a documented response code of #{expected_response_code} at path"\
73
+ " #{method} #{path}"
74
+ elsif method == "get" && swagger_checker.fragment(path, method, expected_response_code).nil?
75
+ errors << "Swagger doc: #{swagger_checker.swagger_path} missing"\
76
+ " response model for get request with #{path} for code"/
77
+ " #{expected_response_code}"
78
+ end
79
+ end
80
+
81
+ def check_status_code
82
+ if response.status != expected_response_code
83
+ errors << "Path #{path} did not respond with expected status code."\
84
+ " Expected #{expected_response_code} got #{response.status}"
85
+ end
86
+ end
87
+
88
+ def check_response_is_valid(swagger_checker)
89
+ swagger_errors = swagger_checker.has_matching_document_for(
90
+ path, method, response.status, response_body
91
+ )
92
+ unless swagger_errors.empty?
93
+ errors.concat(
94
+ swagger_errors.map do |e|
95
+ e.sub("'#", "'#{full_path(swagger_checker)}#").gsub(
96
+ /^The property|in schema.*$/,''
97
+ )
98
+ end
99
+ )
100
+ end
101
+ end
102
+
103
+ def response_body
104
+ JSON.parse(response.body) unless response.body.blank?
105
+ end
106
+
107
+ def has_errors?
108
+ !errors.empty?
109
+ end
110
+
111
+ def failure_message
112
+ errors.join(" ")
113
+ end
114
+
115
+ def errors
116
+ @errors ||= []
117
+ end
118
+
119
+ def description
120
+ "validate that #{method} #{path} returns #{expected_response_code}"
121
+ end
122
+
123
+ # Required by ActionDispatch::Integration::Runner
124
+ def app
125
+ ::Rails.application
126
+ end
127
+
128
+ # Required by rails
129
+ def reset_template_assertion
130
+ end
131
+ end
132
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apivore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Horn
@@ -14,158 +14,144 @@ dependencies:
14
14
  name: json-schema
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 2.5.1
19
+ version: '2.5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: 2.5.1
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec-expectations
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - "~>"
45
+ - - ~>
32
46
  - !ruby/object:Gem::Version
33
47
  version: '3.1'
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - "~>"
52
+ - - ~>
39
53
  - !ruby/object:Gem::Version
40
54
  version: '3.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec-mocks
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ~>
46
60
  - !ruby/object:Gem::Version
47
61
  version: '3.1'
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ~>
53
67
  - !ruby/object:Gem::Version
54
68
  version: '3.1'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: actionpack
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - "~>"
73
+ - - ~>
60
74
  - !ruby/object:Gem::Version
61
75
  version: '4'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - "~>"
80
+ - - ~>
67
81
  - !ruby/object:Gem::Version
68
82
  version: '4'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: hashie
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - ">="
87
+ - - ~>
74
88
  - !ruby/object:Gem::Version
75
- version: 3.3.1
89
+ version: '3.3'
76
90
  type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - ">="
94
+ - - ~>
81
95
  - !ruby/object:Gem::Version
82
- version: 3.3.1
96
+ version: '3.3'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: pry
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - ">="
101
+ - - ~>
88
102
  - !ruby/object:Gem::Version
89
103
  version: '0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - ">="
108
+ - - ~>
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rake
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - ">="
115
+ - - ~>
102
116
  - !ruby/object:Gem::Version
103
- version: '0'
117
+ version: '10.3'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - ">="
122
+ - - ~>
109
123
  - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: rspec
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: '10.3'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rspec-rails
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - ~>
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
131
+ version: '3'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - ~>
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: '3'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: activesupport
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
- - !ruby/object:Gem::Dependency
154
- name: test-unit
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - ">="
143
+ - - ~>
158
144
  - !ruby/object:Gem::Version
159
- version: '0'
145
+ version: '4'
160
146
  type: :development
161
147
  prerelease: false
162
148
  version_requirements: !ruby/object:Gem::Requirement
163
149
  requirements:
164
- - - ">="
150
+ - - ~>
165
151
  - !ruby/object:Gem::Version
166
- version: '0'
167
- description: Automatically tests your API using its Swagger description of end-points,
168
- models, and query parameters.
152
+ version: '4'
153
+ description: Tests your rails API using its Swagger description of end-points, models,
154
+ and query parameters.
169
155
  email: charles.horn@gmail.com
170
156
  executables: []
171
157
  extensions: []
@@ -173,10 +159,17 @@ extra_rdoc_files: []
173
159
  files:
174
160
  - data/swagger_2.0_schema.json
175
161
  - lib/apivore.rb
176
- - lib/apivore/rspec_builder.rb
162
+ - lib/apivore/all_routes_tested_validator.rb
163
+ - lib/apivore/fragment.rb
164
+ - lib/apivore/rspec_helpers.rb
177
165
  - lib/apivore/rspec_matchers.rb
166
+ - lib/apivore/swagger.rb
167
+ - lib/apivore/swagger_checker.rb
168
+ - lib/apivore/validator.rb
178
169
  homepage: http://github.com/westfieldlabs/apivore
179
- licenses: []
170
+ licenses:
171
+ - Apache 2.0
172
+ - MIT
180
173
  metadata: {}
181
174
  post_install_message:
182
175
  rdoc_options: []
@@ -184,20 +177,18 @@ require_paths:
184
177
  - lib
185
178
  required_ruby_version: !ruby/object:Gem::Requirement
186
179
  requirements:
187
- - - ">="
180
+ - - ! '>='
188
181
  - !ruby/object:Gem::Version
189
182
  version: '0'
190
183
  required_rubygems_version: !ruby/object:Gem::Requirement
191
184
  requirements:
192
- - - ">="
185
+ - - ! '>='
193
186
  - !ruby/object:Gem::Version
194
187
  version: '0'
195
188
  requirements: []
196
189
  rubyforge_project:
197
- rubygems_version: 2.4.6
190
+ rubygems_version: 2.4.5
198
191
  signing_key:
199
192
  specification_version: 4
200
- summary: Automatically tests your API using its Swagger description of end-points,
201
- models, and query parameters.
193
+ summary: Tests your API against its Swagger 2.0 spec
202
194
  test_files: []
203
- has_rdoc:
@@ -1,114 +0,0 @@
1
- require 'apivore/rspec_matchers'
2
- require 'action_controller'
3
- require 'action_dispatch'
4
- require 'rspec/mocks'
5
- require 'hashie'
6
-
7
- module Apivore
8
- module RspecBuilder
9
- include Apivore::RspecMatchers
10
- include ActionDispatch::Integration
11
- include RSpec::Mocks::ExampleMethods
12
-
13
- @@setups ||= {}
14
-
15
- @@master_swagger_uri = nil
16
-
17
- # Setup tests against a combination of path, method, and response.
18
- # - *keys -> A combination of path, method, and/or response. Blank '' for base setup.
19
- # - &block -> Code block to execute to setup the test. A hash of path subsitution parameters can be returned if required.
20
- # All matching code blocks are executed, and substitution parameters are merged in order of specificity.
21
- def apivore_setup(*keys, &block)
22
- @@setups[keys.join] = block
23
- end
24
-
25
- def get_apivore_setup(path, method, response)
26
- keys_to_search = [
27
- '', # base setup key
28
- response,
29
- method,
30
- path,
31
- method + response,
32
- path + response,
33
- path + method,
34
- path + method + response
35
- ]
36
- final_result = {}
37
- keys_to_search.each do |k|
38
- setup = @@setups[k]
39
- if setup
40
- result = instance_eval &setup
41
- final_result.merge!(result) if result.is_a?(Hash)
42
- end
43
- end
44
- final_result
45
- end
46
-
47
- def apivore_build_path(path, data)
48
- path.scan(/\{([^\}]*)\}/).each do |param|
49
- key = param.first
50
- if data && data[key]
51
- path = path.gsub "{#{key}}", data[key].to_s
52
- else
53
- raise URI::InvalidURIError, "No substitution data found for {#{key}} to test the path #{path}.\nAdd it via an:\n apivore_setup '<path>', '<method>', '<response>' do\n { '#{key}' => <value> }\n end\nblock in your specs.", caller
54
- end
55
- end
56
- path + (data['_query_string'] ? "?#{data['_query_string']}" : '')
57
- end
58
-
59
- def apivore_check_consistency_with_swagger_at(uri, current_service = nil)
60
- @@current_service = current_service
61
- @@master_swagger_uri = uri
62
- end
63
-
64
- def apivore_swagger(swagger_path)
65
- session = ActionDispatch::Integration::Session.new(Rails.application)
66
- begin
67
- session.get swagger_path
68
- rescue
69
- # TODO: make this fail inside rspec test execution rather than immediately raise an exception.
70
- # ALSO, handle other scenarios where we can't get a response to generate tests, e.g 500s, invalid formats etc
71
- raise "Unable to perform GET request for swagger json: #{swagger_path} - #{$!}."
72
- end
73
- Apivore::Swagger.new JSON.parse(session.response.body)
74
- end
75
-
76
- def validate(swagger_path)
77
-
78
- describe "swagger documentation" do
79
- before { get swagger_path }
80
- subject { body }
81
- it { should be_valid_swagger }
82
- it { should have_models_for_all_get_endpoints }
83
- if @@master_swagger_uri
84
- req = Net::HTTP.get(@@master_swagger_uri, "/swagger.json")
85
- master_swagger = JSON.parse(req)
86
- it { should be_consistent_with_swagger_definitions master_swagger, @@current_service }
87
- end
88
- end
89
-
90
- swagger = apivore_swagger(swagger_path)
91
- swagger.each_response do |path, method, response_code, fragment|
92
- describe "path #{path} method #{method} response #{response_code}" do
93
- it "responds with the specified models" do
94
- setup_data = get_apivore_setup(path, method, response_code)
95
- full_path = apivore_build_path(swagger.base_path + path, setup_data)
96
- # e.g., get(full_path)
97
- begin
98
- send(method, full_path, setup_data['_data'] || {}, setup_data['_headers'] || {})
99
- rescue
100
- raise "Unable to #{method} #{full_path} -- invalid response from server: #{$!}."
101
- end
102
- expect(response).to have_http_status(response_code), "expected #{response_code} array, got #{response.status}: #{response.body}"
103
-
104
- if fragment
105
- expect(response.body).to conform_to_the_documented_model_for(swagger, fragment)
106
- end
107
-
108
- end
109
- end
110
- end
111
-
112
- end
113
- end
114
- end