rspec_api_blueprint_matchers 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +33 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +26 -0
- data/.ruby-version +1 -0
- data/Rakefile +2 -1
- data/config/rubocop/.lint_rubocop.yml +749 -0
- data/config/rubocop/.metrics_rubocop.yml +94 -0
- data/config/rubocop/.performance_rubocop.yml +323 -0
- data/config/rubocop/.rails_rubocop.yml +256 -0
- data/config/rubocop/.style_rubocop.yml +2299 -0
- data/docker-compose.yml +17 -0
- data/lib/rspec_api_blueprint_matchers.rb +1 -0
- data/lib/rspec_apib/config.rb +1 -0
- data/lib/rspec_apib/elements/annotation.rb +1 -0
- data/lib/rspec_apib/elements/array.rb +1 -0
- data/lib/rspec_apib/elements/asset.rb +1 -0
- data/lib/rspec_apib/elements/base.rb +11 -10
- data/lib/rspec_apib/elements/category.rb +2 -1
- data/lib/rspec_apib/elements/copy.rb +2 -1
- data/lib/rspec_apib/elements/data_structure.rb +2 -0
- data/lib/rspec_apib/elements/href_variables.rb +3 -2
- data/lib/rspec_apib/elements/http_headers.rb +5 -4
- data/lib/rspec_apib/elements/http_message_payload.rb +3 -2
- data/lib/rspec_apib/elements/http_request.rb +16 -10
- data/lib/rspec_apib/elements/http_response.rb +3 -1
- data/lib/rspec_apib/elements/http_transaction.rb +4 -3
- data/lib/rspec_apib/elements/member.rb +3 -2
- data/lib/rspec_apib/elements/object.rb +2 -1
- data/lib/rspec_apib/elements/parse_result.rb +1 -0
- data/lib/rspec_apib/elements/resource.rb +1 -0
- data/lib/rspec_apib/elements/source_map.rb +1 -0
- data/lib/rspec_apib/elements/string.rb +1 -0
- data/lib/rspec_apib/elements/templated_href.rb +1 -0
- data/lib/rspec_apib/elements/transition.rb +1 -0
- data/lib/rspec_apib/elements.rb +1 -0
- data/lib/rspec_apib/extractors/http_transaction.rb +1 -0
- data/lib/rspec_apib/extractors/resource.rb +1 -0
- data/lib/rspec_apib/extractors.rb +1 -0
- data/lib/rspec_apib/parser.rb +2 -1
- data/lib/rspec_apib/request.rb +7 -10
- data/lib/rspec_apib/response.rb +2 -2
- data/lib/rspec_apib/rspec.rb +4 -3
- data/lib/rspec_apib/transaction_coverage_report.rb +4 -3
- data/lib/rspec_apib/transaction_coverage_validator.rb +4 -3
- data/lib/rspec_apib/transaction_validator.rb +1 -0
- data/lib/rspec_apib/transcluder.rb +2 -2
- data/lib/rspec_apib/version.rb +2 -1
- data/lib/rspec_apib.rb +2 -1
- data/lib/transcluder.rb +1 -0
- data/rspec_api_blueprint_matchers.gemspec +6 -4
- metadata +25 -2
data/docker-compose.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
version: '2'
|
2
|
+
|
3
|
+
services:
|
4
|
+
test:
|
5
|
+
image: rspec_api_blueprint_matchers
|
6
|
+
build:
|
7
|
+
context: .
|
8
|
+
dockerfile: Dockerfile
|
9
|
+
command: bash -c "bundle install && bundle exec rspec"
|
10
|
+
volumes:
|
11
|
+
- .:/app
|
12
|
+
- rubygems_cache:/rubygems
|
13
|
+
environment:
|
14
|
+
GEM_HOME: '/rubygems'
|
15
|
+
BUNDLE_PATH: '/rubygems'
|
16
|
+
volumes:
|
17
|
+
rubygems_cache:
|
data/lib/rspec_apib/config.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module RSpecApib
|
2
3
|
module Element
|
3
|
-
|
4
|
+
BaseStruct = Struct.new(:element, :meta, :attributes, :content, :parent)
|
4
5
|
|
5
|
-
|
6
|
+
# A base class for all objects in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
7
|
+
class Base < BaseStruct
|
8
|
+
def inspect
|
6
9
|
"##{self.class} (element: #{element}, meta: #{meta}, attributes: #{attributes.inspect}, content: #{content.inspect}, parent: ##{parent.class})"
|
7
10
|
end
|
8
11
|
|
@@ -14,11 +17,11 @@ module RSpecApib
|
|
14
17
|
|
15
18
|
def self.parse(node_or_nodes, index:, parent:, klass: nil)
|
16
19
|
return node_or_nodes.map { |node| parse(node, index: index, parent: parent) } if node_or_nodes.is_a?(::Array)
|
17
|
-
return transformed_basic_hash(node_or_nodes, index: index, parent: parent) if
|
18
|
-
return node_or_nodes unless !klass.nil? ||
|
20
|
+
return transformed_basic_hash(node_or_nodes, index: index, parent: parent) if basic_hash?(node_or_nodes)
|
21
|
+
return node_or_nodes unless !klass.nil? || base_element?(node_or_nodes)
|
19
22
|
hash = node_or_nodes
|
20
23
|
klass_name = klass
|
21
|
-
klass_name ||= hash["element"].slice(0,1).capitalize + hash["element"].slice(1..-1).
|
24
|
+
klass_name ||= hash["element"].slice(0, 1).capitalize + hash["element"].slice(1..-1).delete(" ")
|
22
25
|
return node_or_nodes unless RSpecApib::Element.const_defined?(klass_name)
|
23
26
|
klass = RSpecApib::Element.const_get(klass_name)
|
24
27
|
index[klass] ||= []
|
@@ -61,12 +64,10 @@ module RSpecApib
|
|
61
64
|
{}
|
62
65
|
end
|
63
66
|
|
64
|
-
def self.
|
67
|
+
def self.base_element?(node)
|
65
68
|
node.is_a?(::Hash) && node.keys.include?("element")
|
66
69
|
end
|
67
70
|
|
68
|
-
private
|
69
|
-
|
70
71
|
def self.attributes_schema
|
71
72
|
{}
|
72
73
|
end
|
@@ -81,8 +82,8 @@ module RSpecApib
|
|
81
82
|
node
|
82
83
|
end
|
83
84
|
|
84
|
-
def self.
|
85
|
-
node.is_a?(::Hash) && !
|
85
|
+
def self.basic_hash?(node)
|
86
|
+
node.is_a?(::Hash) && !base_element?(node)
|
86
87
|
end
|
87
88
|
|
88
89
|
def self.transformed_basic_hash(node, index:, parent:)
|
@@ -1,13 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "addressable"
|
2
3
|
module RSpecApib
|
3
4
|
module Element
|
5
|
+
# Represents a collection of href variables in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
4
6
|
class HrefVariables < Base
|
5
7
|
def [](key)
|
6
|
-
member = content.find {|h| h.is_a?(Member) && h.content.key?(key) }
|
8
|
+
member = content.find { |h| h.is_a?(Member) && h.content.key?(key) }
|
7
9
|
return nil if member.nil?
|
8
10
|
member.content[key]
|
9
11
|
end
|
10
|
-
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module RSpecApib
|
2
3
|
module Element
|
4
|
+
# Represents a collection of http headers in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
3
5
|
class HttpHeaders < Base
|
4
6
|
def [](key)
|
5
|
-
member = content.find {|h| h.is_a?(Member) && h.content.key?(key) }
|
7
|
+
member = content.find { |h| h.is_a?(Member) && h.content.key?(key) }
|
6
8
|
return nil if member.nil?
|
7
9
|
member.content[key]
|
8
10
|
end
|
9
11
|
|
10
12
|
def each_pair
|
11
|
-
content.select {|h| h.is_a?(Member)}.each do |header|
|
13
|
+
content.select { |h| h.is_a?(Member) }.each do |header|
|
12
14
|
yield header.key, header.value
|
13
15
|
end
|
14
16
|
end
|
@@ -16,12 +18,11 @@ module RSpecApib
|
|
16
18
|
def keep_if
|
17
19
|
results = dup
|
18
20
|
results.content = []
|
19
|
-
content.select {|h| h.is_a?(Member)}.each do |header|
|
21
|
+
content.select { |h| h.is_a?(Member) }.each do |header|
|
20
22
|
results.content << header if yield header.key, header.value
|
21
23
|
end
|
22
24
|
results
|
23
25
|
end
|
24
|
-
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "json-schema"
|
2
3
|
require "rspec_apib/elements/http_message_payload"
|
3
4
|
module RSpecApib
|
4
5
|
module Element
|
6
|
+
# Represents a http message payload in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
5
7
|
class HttpMessagePayload < Base
|
6
|
-
|
7
8
|
# The content type if defined else nil
|
8
9
|
# @return [String | NilClass] The content type header or nil
|
9
10
|
def content_type
|
@@ -31,7 +32,7 @@ module RSpecApib
|
|
31
32
|
reason: "Schema validation failure",
|
32
33
|
details: errors
|
33
34
|
}
|
34
|
-
|
35
|
+
[failure_reason]
|
35
36
|
end
|
36
37
|
|
37
38
|
private
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "json-schema"
|
2
3
|
require "rspec_apib/elements/http_message_payload"
|
3
4
|
module RSpecApib
|
4
5
|
module Element
|
6
|
+
# Represents a http request in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
5
7
|
class HttpRequest < HttpMessagePayload
|
6
8
|
|
7
9
|
# Indicates if the incoming request matches the method, path and path vars
|
@@ -9,8 +11,8 @@ module RSpecApib
|
|
9
11
|
# @return [Boolean] true if matches else false
|
10
12
|
def matches?(request, options: {})
|
11
13
|
matches_method?(request) &&
|
12
|
-
|
13
|
-
|
14
|
+
matches_path?(request) &&
|
15
|
+
matches_headers?(request, options)
|
14
16
|
end
|
15
17
|
|
16
18
|
# Inherit href and hrefVariables from any ancestor
|
@@ -30,10 +32,16 @@ module RSpecApib
|
|
30
32
|
attributes && attributes["href"].to_s
|
31
33
|
end
|
32
34
|
|
35
|
+
def self.attributes_schema
|
36
|
+
{
|
37
|
+
href: "TemplatedHref"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
33
41
|
private
|
34
42
|
|
35
|
-
def matches_headers?(request_or_response,
|
36
|
-
headers =
|
43
|
+
def matches_headers?(request_or_response, _options)
|
44
|
+
headers = compared_headers
|
37
45
|
return true if headers.nil?
|
38
46
|
headers.each_pair do |header_key, header_value|
|
39
47
|
return false unless request_or_response.headers.key?(header_key) &&
|
@@ -41,6 +49,10 @@ module RSpecApib
|
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
52
|
+
def compared_headers
|
53
|
+
attributes && attributes["headers"] && attributes["headers"].keep_if {|k, _v| k == "Content-Type" || k == "Accept"}
|
54
|
+
end
|
55
|
+
|
44
56
|
def matches_method?(request)
|
45
57
|
attributes && attributes["method"] && attributes["method"].downcase.to_sym == request.request_method
|
46
58
|
end
|
@@ -48,12 +60,6 @@ module RSpecApib
|
|
48
60
|
def matches_path?(request)
|
49
61
|
attributes && attributes["href"] && attributes["href"].matches_path?(request)
|
50
62
|
end
|
51
|
-
|
52
|
-
def self.attributes_schema
|
53
|
-
{
|
54
|
-
href: "TemplatedHref"
|
55
|
-
}
|
56
|
-
end
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "rspec_apib/elements/http_message_payload"
|
2
3
|
module RSpecApib
|
3
4
|
module Element
|
5
|
+
# Represents a http response in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
4
6
|
class HttpResponse < HttpMessagePayload
|
5
7
|
# Indicates if the incoming request matches the method, path and path vars
|
6
8
|
# @param [::RSpecApib::Request] The incoming request - normalized
|
7
9
|
# @return [Boolean] true if matches else false
|
8
10
|
def matches?(response, options: {})
|
9
11
|
matches_status?(response) &&
|
10
|
-
|
12
|
+
matches_content_type?(response)
|
11
13
|
end
|
12
14
|
|
13
15
|
def status
|
@@ -1,15 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module RSpecApib
|
2
3
|
module Element
|
4
|
+
# Represents a http transaction in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
3
5
|
class HttpTransaction < Base
|
4
|
-
|
5
6
|
def matches?(request_in, response_in, options: {})
|
6
7
|
request.matches?(request_in, options: options) && response.matches?(response_in, options: options)
|
7
8
|
end
|
8
9
|
|
9
10
|
def potential_match?(path:, request_method:, content_type:)
|
10
11
|
potential_match_content_type?(content_type) &&
|
11
|
-
|
12
|
-
|
12
|
+
(request_method == :any || request_method == request.request_method) &&
|
13
|
+
(path == :any || request.path == path)
|
13
14
|
end
|
14
15
|
|
15
16
|
def validate_schema(request_in, response_in, validate_request_schema: :always, validate_response_schema: :always)
|
@@ -1,11 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module RSpecApib
|
2
3
|
module Element
|
4
|
+
# Represents a member in api-elements (http://api-elements.readthedocs.io/en/latest/)
|
3
5
|
class Member < Base
|
4
|
-
|
5
6
|
def self.from_hash(hash, index:, parent:)
|
6
7
|
child = super
|
7
8
|
content = child.content
|
8
|
-
child.content = {content["key"] => content["value"]}
|
9
|
+
child.content = { content["key"] => content["value"] }
|
9
10
|
child
|
10
11
|
end
|
11
12
|
|
data/lib/rspec_apib/elements.rb
CHANGED
data/lib/rspec_apib/parser.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "rspec_apib/transcluder"
|
2
3
|
require "rspec_apib/extractors"
|
3
4
|
require "rspec_apib/elements"
|
@@ -65,7 +66,7 @@ module RSpecApib
|
|
65
66
|
|
66
67
|
def call_parser(file)
|
67
68
|
op = nil
|
68
|
-
Open3.popen3("#{bin_path} -f json") do |stdin, stdout,
|
69
|
+
Open3.popen3("#{bin_path} -f json") do |stdin, stdout, _stderr, wait_thr|
|
69
70
|
transcluder.each_line(file) do |line|
|
70
71
|
stdin.write line
|
71
72
|
end
|
data/lib/rspec_apib/request.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "forwardable"
|
1
3
|
module RSpecApib
|
2
4
|
class Request
|
5
|
+
extend Forwardable
|
3
6
|
def initialize(request)
|
4
7
|
self.raw_request = request
|
5
8
|
end
|
@@ -8,19 +11,15 @@ module RSpecApib
|
|
8
11
|
raw_request.method
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
|
-
raw_request.url
|
13
|
-
end
|
14
|
+
delegate url: :raw_request
|
14
15
|
|
15
16
|
def validate_body_with_json_schema?
|
16
|
-
request_method != :get &&
|
17
|
+
request_method != :get && json?
|
17
18
|
end
|
18
19
|
|
19
20
|
# The request body
|
20
21
|
# @return [String] The request body - always as a string
|
21
|
-
|
22
|
-
raw_request.body
|
23
|
-
end
|
22
|
+
delegate body: :raw_request
|
24
23
|
|
25
24
|
def content_type
|
26
25
|
headers["Content-Type"]
|
@@ -30,15 +29,13 @@ module RSpecApib
|
|
30
29
|
raw_request.request_headers
|
31
30
|
end
|
32
31
|
|
33
|
-
|
34
32
|
private
|
35
33
|
|
36
34
|
attr_accessor :raw_request
|
37
35
|
|
38
|
-
def
|
36
|
+
def json?
|
39
37
|
content_type =~ /json/
|
40
38
|
end
|
41
39
|
|
42
|
-
|
43
40
|
end
|
44
41
|
end
|