rspec-api-matchers 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -19
  3. data/lib/rspec-api/matchers.rb +38 -21
  4. data/lib/rspec-api/matchers/attributes/have_attributes.rb +23 -0
  5. data/lib/rspec-api/matchers/attributes/matcher.rb +101 -0
  6. data/lib/rspec-api/matchers/collection/be_a_collection.rb +20 -0
  7. data/lib/rspec-api/matchers/collection/matcher.rb +19 -0
  8. data/lib/rspec-api/matchers/content_type/have_content_type.rb +24 -0
  9. data/lib/rspec-api/matchers/content_type/matcher.rb +23 -0
  10. data/lib/rspec-api/matchers/filter/be_filtered.rb +20 -0
  11. data/lib/rspec-api/matchers/filter/matcher.rb +57 -0
  12. data/lib/rspec-api/matchers/headers/have_headers.rb +20 -0
  13. data/lib/rspec-api/matchers/headers/matcher.rb +28 -0
  14. data/lib/rspec-api/matchers/json/be_valid_json.rb +20 -0
  15. data/lib/rspec-api/matchers/json/matcher.rb +41 -0
  16. data/lib/rspec-api/matchers/jsonp/be_wrapped_in_callback.rb +24 -0
  17. data/lib/rspec-api/matchers/jsonp/matcher.rb +23 -0
  18. data/lib/rspec-api/matchers/page_links/have_page_links.rb +25 -0
  19. data/lib/rspec-api/matchers/page_links/matcher.rb +18 -0
  20. data/lib/rspec-api/matchers/response/be_a_valid_response.rb +20 -0
  21. data/lib/rspec-api/matchers/response/matcher.rb +52 -0
  22. data/lib/rspec-api/matchers/sort/be_sorted.rb +20 -0
  23. data/lib/rspec-api/matchers/sort/matcher.rb +56 -0
  24. data/lib/rspec-api/matchers/status/have_status.rb +25 -0
  25. data/lib/rspec-api/matchers/status/matcher.rb +46 -0
  26. data/lib/rspec-api/matchers/version.rb +1 -1
  27. metadata +24 -18
  28. data/lib/rspec-api/dsl/be_a_collection.rb +0 -24
  29. data/lib/rspec-api/dsl/be_a_jsonp.rb +0 -24
  30. data/lib/rspec-api/dsl/be_filtered.rb +0 -24
  31. data/lib/rspec-api/dsl/be_sorted.rb +0 -24
  32. data/lib/rspec-api/dsl/have_attributes.rb +0 -37
  33. data/lib/rspec-api/dsl/have_prev_page_link.rb +0 -20
  34. data/lib/rspec-api/dsl/have_status.rb +0 -20
  35. data/lib/rspec-api/dsl/include_content_type.rb +0 -24
  36. data/lib/rspec-api/matchers/attributes.rb +0 -171
  37. data/lib/rspec-api/matchers/collection.rb +0 -42
  38. data/lib/rspec-api/matchers/content_type.rb +0 -26
  39. data/lib/rspec-api/matchers/filter.rb +0 -58
  40. data/lib/rspec-api/matchers/jsonp.rb +0 -27
  41. data/lib/rspec-api/matchers/prev_page_link.rb +0 -23
  42. data/lib/rspec-api/matchers/sort.rb +0 -65
  43. data/lib/rspec-api/matchers/status.rb +0 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52d99afa65e3dc027e30e06fe06e5abdb6ca7b77
4
- data.tar.gz: 670f2f96336bf3b7ffdd41434f7a756fc358da4c
3
+ metadata.gz: 7a9cb40b04968465419bdc9816a1002fe6a29761
4
+ data.tar.gz: f1ca85d85451586be343f9edf7bb2d6405590578
5
5
  SHA512:
6
- metadata.gz: 76fcda35ccae07f245892ec5773f20dc43c875714cf2bda6f7f44efdcafafc98e493739b2d4e0c7056d91f3aabe1055397d95c3e278ec3bc11983e345fa397db
7
- data.tar.gz: 8e71adfec9954e778684d3ecce2ac4bce83c9319cb7a5f0acd7dfaf9efa6363d8ddc60101d646aa43a30c08ee5b8869cd9d4069ac4ed525e3eab927d68ff01dc
6
+ metadata.gz: a5315d2e00d92d287059754dbef2aa689305f43ed5e66b5a580b332aed9430a1cc4741b9a1f96b768f5c074e45910c1d31a4b9c6ad04301461407d6f1bdfcbbd
7
+ data.tar.gz: 59b6add713ca2f7988ffca56bddbb5fb64efc069066f01e283ac135ed00382fcf3834f573c7dba9d7920c59fad110ce850c903e4ccdc54738eb267ae429dae89
data/README.md CHANGED
@@ -1,42 +1,47 @@
1
- RSpec API Matchers
1
+ RSpecApi::Matchers
2
2
  ==================
3
3
 
4
4
  RSpecApi::Matchers lets you express outcomes on the response of web APIs.
5
5
 
6
6
  expect(response).to have_status(:not_found)
7
7
 
8
- More documentation and examples about RSpec Api are available at [http://rspec-api.github.io](http://rspec-api.github.io)
8
+ More documentation and examples about RSpecApi are available at [http://rspec-api.github.io](http://rspec-api.github.io)
9
9
 
10
10
  [![Build Status](https://travis-ci.org/rspec-api/rspec-api-matchers.png?branch=master)](https://travis-ci.org/rspec-api/rspec-api-matchers)
11
11
  [![Code Climate](https://codeclimate.com/github/rspec-api/rspec-api-matchers.png)](https://codeclimate.com/github/rspec-api/rspec-api-matchers)
12
12
  [![Coverage Status](https://coveralls.io/repos/rspec-api/rspec-api-matchers/badge.png)](https://coveralls.io/r/rspec-api/rspec-api-matchers)
13
13
  [![Dependency Status](https://gemnasium.com/rspec-api/rspec-api-matchers.png)](https://gemnasium.com/rspec-api/rspec-api-matchers)
14
14
 
15
- Install
16
- -------
17
-
18
- Add `gem 'rspec-api-matchers'` to your `Gemfile` and run `bundle`.
19
- Or install yourself by running `gem install rspec-api-matchers`.
20
-
21
- Version changes respect [Semantic Versioning](http://semver.org).
22
- To ensure `bundle update` won't cause problems in your repository,
23
- always indicate the patch version until version 1.0.0 is released, e.g.:
24
-
25
- gem 'rspec-api-matchers', '~> 0.5.0'
26
-
27
-
28
15
  Available matchers
29
16
  ------------------
30
17
 
31
18
  expect(response).to have_status(:ok)
32
- expect(response).to include_content_type(:json)
33
- expect(response).to have_prev_page_link
19
+ expect(response).to have_content_type(:json)
20
+ expect(response).to have_page_links
34
21
  expect(response).to be_a_collection
35
- expect(response).to be_a_jsonp('alert')
22
+ expect(response).to be_wrapped_in_callback('alert')
36
23
  expect(response).to be_sorted(by: :id, verse: :desc)
37
- expect(response).to be_filtered(10, by: :id)
24
+ expect(response).to be_filtered(by: :id, value: 10)
38
25
  expect(response).to have_attributes(id: {value: 1.2}, url: {type: {string: :url}})
39
26
 
27
+
28
+ How to install
29
+ ==============
30
+
31
+ To install on your system, run `gem install rspec-api-matchers`.
32
+ To use inside a bundled Ruby project, add this line to the Gemfile:
33
+
34
+ gem 'rspec-api-matchers', '~> 0.5.0'
35
+
36
+ The rspec-api-matchers gem follows [Semantic Versioning](http://semver.org).
37
+ Any new release that is fully backward-compatible bumps the *patch* version (0.5.1).
38
+ Any new version that breaks compatibility bumps the *minor* version (0.6.0)
39
+
40
+ Indicating the full version in your Gemfile (*major*.*minor*.*patch*) guarantees
41
+ that your project won’t occur in any error when you `bundle update` and a new
42
+ version of RSpecApi::Matchers is released.
43
+
44
+
40
45
  How to contribute
41
46
  =================
42
47
 
@@ -1,27 +1,44 @@
1
- require 'rspec-api/dsl/have_status'
2
- require 'rspec-api/dsl/include_content_type'
3
- require 'rspec-api/dsl/have_prev_page_link'
4
- require 'rspec-api/dsl/be_a_jsonp'
5
- require 'rspec-api/dsl/be_sorted'
6
- require 'rspec-api/dsl/be_a_collection'
7
- require 'rspec-api/dsl/be_filtered'
8
- require 'rspec-api/dsl/have_attributes'
9
-
10
- require 'rspec/matchers'
11
-
12
- # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
13
- # makes RSpecApi::Matchers::Status available as expect(...).to match_status
1
+ require 'rspec/core'
2
+ require 'rspec-api/matchers/attributes/have_attributes'
3
+ require 'rspec-api/matchers/collection/be_a_collection'
4
+ require 'rspec-api/matchers/content_type/have_content_type'
5
+ require 'rspec-api/matchers/filter/be_filtered'
6
+ require 'rspec-api/matchers/json/be_valid_json'
7
+ require 'rspec-api/matchers/jsonp/be_wrapped_in_callback'
8
+ require 'rspec-api/matchers/page_links/have_page_links'
9
+ require 'rspec-api/matchers/response/be_a_valid_response'
10
+ require 'rspec-api/matchers/sort/be_sorted'
11
+ require 'rspec-api/matchers/status/have_status'
14
12
 
15
13
  module RSpecApi
16
14
  module Matchers
17
- module DSL
18
- end
15
+ include Attributes
16
+ include Collection
17
+ include ContentType
18
+ include Filter
19
+ include Json
20
+ include Jsonp
21
+ include PageLinks
22
+ include Response
23
+ include Sort
24
+ include Status
19
25
  end
20
26
  end
21
27
 
22
- module RSpec
23
- module Matchers
24
- # Make RSpecApi::Matchers::DSL methods available inside RSpec
25
- include ::RSpecApi::Matchers::DSL
26
- end
27
- end
28
+ # RSpecApi::Matchers adds matchers to test RESTful APIs.
29
+ #
30
+ # To have these matchers available inside of an RSpec `describe` block, tag that
31
+ # block with the `:rspec_api` metadata:
32
+ #
33
+ # describe "Artists", rspec_api: true do
34
+ # ... # here you can write `expect(response).to have_status :ok`, etc.
35
+ # end
36
+ RSpec.configuration.include RSpecApi::Matchers, rspec_api: true
37
+
38
+ # You can also explicitly include the RSpec::Api module inside the example group:
39
+ #
40
+ # describe "Artists" do
41
+ # include RSpecApi::Matchers
42
+ # ... # here you can write `expect(response).to have_status :ok`, etc.
43
+ # end
44
+ #
@@ -0,0 +1,23 @@
1
+ require 'rspec-api/matchers/attributes/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module Attributes
6
+ # Passes if the response body is either a JSON object or a collection of
7
+ # JSON objects that include all the +attributes+.
8
+ #
9
+ # @example
10
+ #
11
+ # # Passes if the body is an object with a URL-formatted String :site
12
+ # response = OpenStruct.new body: '{"site": "http://www.example.com"}'
13
+ # expect(response).to have_attributes(site: {type: {string: :url}})
14
+ #
15
+ # For more examples check +have_attributes_spec.rb+.
16
+
17
+ # TODO: add &block options
18
+ def have_attributes(*attributes)
19
+ RSpecApi::Matchers::Attributes::Matcher.new *attributes
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,101 @@
1
+ require 'rspec-api/matchers/json/matcher'
2
+ require 'active_support/core_ext/array/wrap'
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ module RSpecApi
6
+ module Matchers
7
+ module Attributes
8
+ class Matcher < Json::Matcher
9
+ attr_accessor :attributes
10
+
11
+ def initialize(*attributes, &block)
12
+ @attributes = to_hash attributes
13
+ end
14
+
15
+ def matches?(response)
16
+ super && has_attributes?(json, attributes)
17
+ end
18
+
19
+ def description
20
+ if attributes.any?
21
+ %Q(have attributes #{attributes})
22
+ else
23
+ %Q(have attributes)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def has_attributes?(items, attrs)
30
+ attrs.deep_symbolize_keys!
31
+ attrs.all?{|name, options| has_attribute? items, name, options}
32
+ end
33
+
34
+ def has_attribute?(items, name, options)
35
+ return false unless Array.wrap(items).all?{|item| item.key? name}
36
+ values = Array.wrap(items).map{|item| item[name]}
37
+ attr_types = Array.wrap(options.fetch :type, :any)
38
+ attr_value = options.fetch :value, :any
39
+ values.all? do |v|
40
+ matches_value?(v, attr_value) && matches_types?(v, attr_types)
41
+ end
42
+ end
43
+
44
+ def matches_value?(value, expected_value)
45
+ case expected_value
46
+ when :any then true
47
+ when Proc then expected_value.call value
48
+ else value == expected_value
49
+ end
50
+ end
51
+
52
+ def matches_types?(value, expected_types)
53
+ expected_types.any?{|type| matches_type_and_format? value, type}
54
+ end
55
+
56
+ def matches_type_and_format?(value, type)
57
+ type = Hash[type, :any] unless type.is_a?(Hash)
58
+ type.any? do |type, format|
59
+ matches_type?(value, type) && matches_format?(value, format)
60
+ end
61
+ end
62
+
63
+ def matches_type?(value, type)
64
+ type_to_classes(type).any?{|klass| value.is_a? klass}
65
+ end
66
+
67
+ def matches_format?(value, format)
68
+ case format
69
+ when :url then value =~ URI::regexp
70
+ when :integer then value.is_a? Integer
71
+ when :timestamp then DateTime.iso8601 value rescue false
72
+ when :email then value =~ %r{(?<name>.+?)@(?<host>.+?)\.(?<domain>.+?)}
73
+ when Hash, Array then value.any? ? has_attributes?(value, to_hash(format)) : true
74
+ when String then matches_format?(value, format.to_sym)
75
+ when :any then true
76
+ end
77
+ end
78
+
79
+ def type_to_classes(type)
80
+ Array.wrap case type
81
+ when :string then String
82
+ when :array then Array
83
+ when :object then Hash
84
+ when :null then NilClass
85
+ when :boolean then [TrueClass, FalseClass]
86
+ when :number then Numeric
87
+ when String then type_to_classes(type.to_sym)
88
+ when :any then Object
89
+ end
90
+ end
91
+
92
+ def to_hash(attrs)
93
+ array = Array.wrap(attrs).map do |item|
94
+ item.is_a?(Hash) ? [item.keys.first, item.values.first] : [item, {}]
95
+ end.flatten
96
+ Hash[*array]
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec-api/matchers/collection/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module Collection
6
+ # Passes if the response body is a collection of JSON objects
7
+ #
8
+ # @example
9
+ #
10
+ # # Passes if the body is a JSON array
11
+ # body = '[{"id": 1}]'
12
+ # expect(OpenStruct.new body: body).to be_a_collection
13
+ #
14
+ # For more examples check +be_a_collection_spec.rb+.
15
+ def be_a_collection
16
+ RSpecApi::Matchers::Collection::Matcher.new
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'rspec-api/matchers/json/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module Collection
6
+ class Matcher < Json::Matcher
7
+
8
+ def matches?(response)
9
+ super
10
+ json.is_a? Array
11
+ end
12
+
13
+ def description
14
+ %Q(be a collection)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ require 'rspec-api/matchers/content_type/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module ContentType
6
+ # Passes if the response headers specify the provided content +type+
7
+ #
8
+ # @example
9
+ #
10
+ # # Passes if the headers matches the provided JSON content type
11
+ # headers ={'Content-Type' => 'application/json; charset=utf-8'}
12
+ # expect(OpenStruct.new headers: headers).to have_content_type(:json)
13
+ #
14
+ # For more examples check +have_content_type_spec.rb+.
15
+ def have_content_type(type = nil)
16
+ content_type = case type
17
+ when :json then 'application/json; charset=utf-8'
18
+ when :any then %r{}
19
+ end
20
+ RSpecApi::Matchers::ContentType::Matcher.new content_type
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require 'rspec-api/matchers/headers/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module ContentType
6
+ class Matcher < Headers::Matcher
7
+ attr_accessor :content_type
8
+
9
+ def initialize(content_type)
10
+ @content_type = content_type
11
+ end
12
+
13
+ def matches?(response)
14
+ super && headers.key?('Content-Type') && content_type.match(headers['Content-Type'])
15
+ end
16
+
17
+ def description
18
+ %Q{include 'Content-Type': '#{content_type}'}
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec-api/matchers/filter/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module Filter
6
+ # Passes if the response body is a non-empty filtered JSON collection
7
+ #
8
+ # @example
9
+ #
10
+ # # Passes if the body only contains objects with ID = 1
11
+ # body = '[{"id": 1}, {"id": 1}, {"id": 1}]'
12
+ # expect(OpenStruct.new body: body).to be_filtered by: :id, value: 1
13
+ #
14
+ # For more examples check +be_filtered_spec.rb+.
15
+ def be_filtered(options = {})
16
+ RSpecApi::Matchers::Filter::Matcher.new options
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ require 'rspec-api/matchers/collection/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module Filter
6
+ class Matcher < Collection::Matcher
7
+ attr_accessor :field, :value, :compare_with
8
+
9
+ def initialize(options = {})
10
+ @field = options[:by]
11
+ @value = options[:value]
12
+ @compare_with = options.fetch :compare_with, :==
13
+ end
14
+
15
+ def matches?(response)
16
+ super && all_objects_have_field? && has_two_objects? && is_filtered?
17
+ end
18
+
19
+ def description
20
+ if field
21
+ %Q(be filtered by #{field}#{compare_with}#{value})
22
+ else
23
+ %Q(be filtered)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def all_objects_have_field?
30
+ if field
31
+ json.all?{|item| item.is_a?(Hash) && item.key?(field)}
32
+ else
33
+ true
34
+ end
35
+ end
36
+
37
+ def has_two_objects?
38
+ if json.length < 2
39
+ msg = "Cannot test filtering on an array with #{json.length} items"
40
+ raise RSpec::Core::Pending::PendingDeclaredInExample.new msg
41
+ else
42
+ true
43
+ end
44
+ end
45
+
46
+ def is_filtered?
47
+ values = json.map{|item| item[field]}
48
+ values.all?{|v| v.send compare_with, value}
49
+ end
50
+
51
+ def reverse?
52
+ ['desc', 'descending'].include? verse.to_s
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec-api/matchers/headers/matcher'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module Headers
6
+ # Passes if the response has a non-empty headers Hash.
7
+ #
8
+ # @example
9
+ #
10
+ # # Passes if the headers include the content length
11
+ # headers = {'Content-Length' => 17372}
12
+ # expect(OpenStruct.new headers: headers).to have_headers
13
+ #
14
+ # For more examples check +have_headers_spec.rb+.
15
+ def have_headers
16
+ RSpecApi::Matchers::Headers::Matcher.new
17
+ end
18
+ end
19
+ end
20
+ end