rspec_api_blueprint_matchers 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Dockerfile +24 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +77 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/codeship-services.yml +7 -0
  13. data/codeship-steps.yml +4 -0
  14. data/lib/rspec_api_blueprint_matchers.rb +1 -0
  15. data/lib/rspec_apib.rb +41 -0
  16. data/lib/rspec_apib/config.rb +17 -0
  17. data/lib/rspec_apib/elements.rb +20 -0
  18. data/lib/rspec_apib/elements/annotation.rb +7 -0
  19. data/lib/rspec_apib/elements/array.rb +9 -0
  20. data/lib/rspec_apib/elements/asset.rb +7 -0
  21. data/lib/rspec_apib/elements/base.rb +97 -0
  22. data/lib/rspec_apib/elements/category.rb +7 -0
  23. data/lib/rspec_apib/elements/copy.rb +7 -0
  24. data/lib/rspec_apib/elements/data_structure.rb +7 -0
  25. data/lib/rspec_apib/elements/href_variables.rb +13 -0
  26. data/lib/rspec_apib/elements/http_headers.rb +27 -0
  27. data/lib/rspec_apib/elements/http_message_payload.rb +45 -0
  28. data/lib/rspec_apib/elements/http_request.rb +59 -0
  29. data/lib/rspec_apib/elements/http_response.rb +35 -0
  30. data/lib/rspec_apib/elements/http_transaction.rb +58 -0
  31. data/lib/rspec_apib/elements/member.rb +21 -0
  32. data/lib/rspec_apib/elements/object.rb +7 -0
  33. data/lib/rspec_apib/elements/parse_result.rb +7 -0
  34. data/lib/rspec_apib/elements/resource.rb +24 -0
  35. data/lib/rspec_apib/elements/source_map.rb +7 -0
  36. data/lib/rspec_apib/elements/string.rb +9 -0
  37. data/lib/rspec_apib/elements/templated_href.rb +43 -0
  38. data/lib/rspec_apib/elements/transition.rb +22 -0
  39. data/lib/rspec_apib/engine.rb +0 -0
  40. data/lib/rspec_apib/extractors.rb +2 -0
  41. data/lib/rspec_apib/extractors/http_transaction.rb +23 -0
  42. data/lib/rspec_apib/extractors/resource.rb +23 -0
  43. data/lib/rspec_apib/parser.rb +79 -0
  44. data/lib/rspec_apib/request.rb +44 -0
  45. data/lib/rspec_apib/response.rb +39 -0
  46. data/lib/rspec_apib/rspec.rb +40 -0
  47. data/lib/rspec_apib/transaction_coverage_report.rb +36 -0
  48. data/lib/rspec_apib/transaction_coverage_validator.rb +49 -0
  49. data/lib/rspec_apib/transaction_validator.rb +44 -0
  50. data/lib/rspec_apib/transcluder.rb +30 -0
  51. data/lib/rspec_apib/version.rb +3 -0
  52. data/lib/transcluder.rb +3 -0
  53. data/rspec_api_blueprint_matchers.gemspec +29 -0
  54. metadata +182 -0
@@ -0,0 +1,7 @@
1
+ module RSpecApib
2
+ module Element
3
+ class DataStructure < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require "addressable"
2
+ module RSpecApib
3
+ module Element
4
+ class HrefVariables < Base
5
+ def [](key)
6
+ member = content.find {|h| h.is_a?(Member) && h.content.key?(key) }
7
+ return nil if member.nil?
8
+ member.content[key]
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module RSpecApib
2
+ module Element
3
+ class HttpHeaders < Base
4
+ def [](key)
5
+ member = content.find {|h| h.is_a?(Member) && h.content.key?(key) }
6
+ return nil if member.nil?
7
+ member.content[key]
8
+ end
9
+
10
+ def each_pair
11
+ content.select {|h| h.is_a?(Member)}.each do |header|
12
+ yield header.key, header.value
13
+ end
14
+ end
15
+
16
+ def keep_if
17
+ results = dup
18
+ results.content = []
19
+ content.select {|h| h.is_a?(Member)}.each do |header|
20
+ results.content << header if yield header.key, header.value
21
+ end
22
+ results
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ require "json-schema"
2
+ require "rspec_apib/elements/http_message_payload"
3
+ module RSpecApib
4
+ module Element
5
+ class HttpMessagePayload < Base
6
+
7
+ # The content type if defined else nil
8
+ # @return [String | NilClass] The content type header or nil
9
+ def content_type
10
+ attributes["headers"] && attributes["headers"]["Content-Type"]
11
+ end
12
+
13
+ def validate_schema(request_or_response, allow_no_schema: false)
14
+ return [] unless request_or_response.validate_body_with_json_schema?
15
+ schema = body_schema_asset
16
+ return [] if schema.nil? && allow_no_schema
17
+ if schema.nil?
18
+ failure_reason = {
19
+ success: false,
20
+ reason: "Missing a body schema",
21
+ details: []
22
+ }
23
+ return [failure_reason]
24
+ end
25
+ schema = JSON.parse schema.content
26
+ errors = JSON::Validator.fully_validate(schema, request_or_response.body)
27
+ return [] if errors.length.zero?
28
+
29
+ failure_reason = {
30
+ success: false,
31
+ reason: "Schema validation failure",
32
+ details: errors
33
+ }
34
+ return [failure_reason]
35
+ end
36
+
37
+ private
38
+
39
+ def body_schema_asset
40
+ content.find { |n| n.is_a?(Asset) && n.meta && n.meta["classes"] && n.meta["classes"].include?("messageBodySchema")}
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ require "json-schema"
2
+ require "rspec_apib/elements/http_message_payload"
3
+ module RSpecApib
4
+ module Element
5
+ class HttpRequest < HttpMessagePayload
6
+
7
+ # Indicates if the incoming request matches the method, path and path vars
8
+ # @param [::RSpecApib::Request] The incoming request - normalized
9
+ # @return [Boolean] true if matches else false
10
+ def matches?(request, options: {})
11
+ matches_method?(request) &&
12
+ matches_path?(request) &&
13
+ matches_headers?(request, options)
14
+ end
15
+
16
+ # Inherit href and hrefVariables from any ancestor
17
+ def self.attrs_to_inherit
18
+ [:href, :hrefVariables, :method]
19
+ end
20
+
21
+ def request_method
22
+ attributes && attributes["method"] && attributes["method"].downcase.to_sym
23
+ end
24
+
25
+ def path
26
+ attributes && attributes["href"] && attributes["href"].path
27
+ end
28
+
29
+ def url
30
+ attributes && attributes["href"].to_s
31
+ end
32
+
33
+ private
34
+
35
+ def matches_headers?(request_or_response, options)
36
+ headers = attributes && attributes["headers"] && attributes["headers"].keep_if {|k, v| k == "Content-Type" || k == "Accept"}
37
+ return true if headers.nil?
38
+ headers.each_pair do |header_key, header_value|
39
+ return false unless request_or_response.headers.key?(header_key) &&
40
+ request_or_response.headers[header_key] == header_value
41
+ end
42
+ end
43
+
44
+ def matches_method?(request)
45
+ attributes && attributes["method"] && attributes["method"].downcase.to_sym == request.request_method
46
+ end
47
+
48
+ def matches_path?(request)
49
+ attributes && attributes["href"] && attributes["href"].matches_path?(request)
50
+ end
51
+
52
+ def self.attributes_schema
53
+ {
54
+ href: "TemplatedHref"
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ require "rspec_apib/elements/http_message_payload"
2
+ module RSpecApib
3
+ module Element
4
+ class HttpResponse < HttpMessagePayload
5
+ # Indicates if the incoming request matches the method, path and path vars
6
+ # @param [::RSpecApib::Request] The incoming request - normalized
7
+ # @return [Boolean] true if matches else false
8
+ def matches?(response, options: {})
9
+ matches_status?(response) &&
10
+ matches_content_type?(response)
11
+ end
12
+
13
+ def status
14
+ attributes["statusCode"]
15
+ end
16
+
17
+ private
18
+
19
+ def matches_status?(response)
20
+ response.status == attributes["statusCode"]
21
+ end
22
+
23
+ def matches_content_type?(response)
24
+ expected_content_type = content_type
25
+ expected_content_type.nil? || (expected_content_type == response.content_type)
26
+ end
27
+
28
+ def self.attributes_schema
29
+ {
30
+ href: "TemplatedHref"
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+ module RSpecApib
2
+ module Element
3
+ class HttpTransaction < Base
4
+
5
+ def matches?(request_in, response_in, options: {})
6
+ request.matches?(request_in, options: options) && response.matches?(response_in, options: options)
7
+ end
8
+
9
+ def potential_match?(path:, request_method:, content_type:)
10
+ potential_match_content_type?(content_type) &&
11
+ (request_method == :any || request_method == request.request_method) &&
12
+ (path == :any || request.path == path)
13
+ end
14
+
15
+ def validate_schema(request_in, response_in, validate_request_schema: :always, validate_response_schema: :always)
16
+ request_errors = validate_request_schema(request_in, validate_request_schema)
17
+ response_errors = validate_response_schema(response_in, validate_response_schema)
18
+ { request_errors: request_errors, response_errors: response_errors }
19
+ end
20
+
21
+ def request
22
+ @request ||= content.find {|r| r.is_a?(HttpRequest)}
23
+ end
24
+
25
+ def response
26
+ @response ||= content.find {|r| r.is_a?(HttpResponse)}
27
+ end
28
+
29
+ private
30
+
31
+ def potential_match_content_type?(content_type)
32
+ content_type == :any || content_type == (response.content_type || request.content_type)
33
+ end
34
+
35
+ def validate_request_schema(request_in, validate_request_schema)
36
+ return [] if validate_request_schema == :never
37
+ request.validate_schema(request_in, allow_no_schema: (validate_request_schema == :when_defined))
38
+ end
39
+
40
+ def validate_response_schema(response_in, validate_response_schema)
41
+ return [] if validate_response_schema == :never
42
+ response.validate_schema(response_in, allow_no_schema: (validate_response_schema == :when_defined))
43
+ end
44
+
45
+ # Inherit href and hrefVariables from any ancestor
46
+ def self.attrs_to_inherit
47
+ [:href, :hrefVariables]
48
+ end
49
+
50
+ def self.attributes_schema
51
+ {
52
+ href: "TemplatedHref"
53
+ }
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ module RSpecApib
2
+ module Element
3
+ class Member < Base
4
+
5
+ def self.from_hash(hash, index:, parent:)
6
+ child = super
7
+ content = child.content
8
+ child.content = {content["key"] => content["value"]}
9
+ child
10
+ end
11
+
12
+ def key
13
+ content.keys.first
14
+ end
15
+
16
+ def value
17
+ content.values.first
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module RSpecApib
2
+ module Element
3
+ class Object < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module RSpecApib
2
+ module Element
3
+ class ParseResult < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ module RSpecApib
2
+ module Element
3
+ # (byebug) parent.parent["attributes"]
4
+ # {"meta"=>[#<struct RSpecApib::Element::Member element="member", meta={"classes"=>["user"]}, attributes={}, content={"FORMAT"=>"1A"}, parent=#<struct RSpecApib::Element::ParseResult element="parseResult", meta=nil, attributes=nil, content=nil, parent=nil>>, #<struct RSpecApib::Element::Member element="member", meta={"classes"=>["user"]}, attributes={}, content={"HOST"=>"http://api.shiftcommerce.com/inventory/v1"}, parent=#<struct RSpecApib::Element::ParseResult element="parseResult", meta=nil, attributes=nil, content=nil, parent=nil>>]}
5
+
6
+ class Resource < Base
7
+ def transitions
8
+ content.select { |item| item.is_a?(Transition) }
9
+ end
10
+
11
+ def categories
12
+ content.select { |item| item.is_a?(Category) }
13
+ end
14
+
15
+ private
16
+
17
+ def self.attributes_schema
18
+ {
19
+ href: "TemplatedHref"
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module RSpecApib
2
+ module Element
3
+ class SourceMap < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module RSpecApib
2
+ module Element
3
+ class String < ::String
4
+ def self.from_hash(hash, index:, parent:)
5
+ new(hash["content"] || "")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ require "addressable"
2
+ module RSpecApib
3
+ module Element
4
+ class TemplatedHref < Base
5
+ # Note this is not really a hash !!
6
+ def self.from_hash(hash, index:, parent:)
7
+ new("templatedHref", nil, nil, hash, parent)
8
+ end
9
+
10
+ def to_s
11
+ content
12
+ end
13
+
14
+ def matches_path?(request)
15
+ tpl = Addressable::Template.new(url)
16
+ result = tpl.extract(request.url)
17
+ !result.nil?
18
+ end
19
+
20
+ def url
21
+ @url ||= File.join(self.class.host_from_parent(parent), content)
22
+ end
23
+
24
+ def path
25
+ a1 = Addressable::URI.parse(url)
26
+ a1.path, a1.query, a1.fragment = nil
27
+ a2 = Addressable::URI.parse(url)
28
+ a2.to_s.gsub(a1.to_s, "")
29
+ end
30
+
31
+ def self.host_from_parent(node)
32
+ return "" if node.nil?
33
+ attrs = node.attributes
34
+ return host_from_parent(node.parent) unless attrs && attrs["meta"]
35
+ host_member = attrs["meta"].find do |member|
36
+ member.is_a?(Member) && member.content.key?("HOST")
37
+ end
38
+ return host_from_parent(node.parent) unless host_member
39
+ host_member.content["HOST"]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ module RSpecApib
2
+ module Element
3
+ class Transition < Base
4
+ def http_transactions
5
+ content.select { |item| item.is_a?(HttpTransaction) }
6
+ end
7
+
8
+ # Inherit href and hrefVariables from any ancestor (normally resource)
9
+ def self.attrs_to_inherit
10
+ [:href, :hrefVariables]
11
+ end
12
+
13
+ private
14
+
15
+ def self.attributes_schema
16
+ {
17
+ href: "TemplatedHref"
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
File without changes
@@ -0,0 +1,2 @@
1
+ require "rspec_apib/extractors/resource"
2
+ require "rspec_apib/extractors/http_transaction"
@@ -0,0 +1,23 @@
1
+ module RSpecApib
2
+ module Extractor
3
+ class HttpTransaction
4
+ def self.call(document)
5
+ collector = []
6
+ find_nodes_within(document, collector: collector)
7
+ collector
8
+ end
9
+
10
+ private
11
+
12
+ def self.find_nodes_within(node, collector:)
13
+ return node.each { |node| find_nodes_within(node, collector: collector)} if node.is_a?(Array)
14
+ return unless node.is_a?(Hash)
15
+ if node["element"] == "httpTransaction"
16
+ collector << node
17
+ elsif node.key?("content")
18
+ find_nodes_within(node["content"], collector: collector)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module RSpecApib
2
+ module Extractor
3
+ class Resource
4
+ def self.call(document)
5
+ collector = []
6
+ find_nodes_within(document, collector: collector)
7
+ collector
8
+ end
9
+
10
+ private
11
+
12
+ def self.find_nodes_within(node, collector:)
13
+ return node.each { |node| find_nodes_within(node, collector: collector)} if node.is_a?(Array)
14
+ return unless node.is_a?(Hash)
15
+ if node["element"] == "resource"
16
+ collector << node
17
+ elsif node.key?("content")
18
+ find_nodes_within(node["content"], collector: collector)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end