rspec-api-matchers 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 79c751c1af951762a1fe11184842ac7959b4e43a
4
- data.tar.gz: c7b91641ccb763315b14cc0e2eadf2e4651a5fe8
3
+ metadata.gz: 480e18da2657efd7c3645a753ce1cc105c8bd4d4
4
+ data.tar.gz: fb4600f19714aa5eb6f0a49b4e45bcc05be3d1ca
5
5
  SHA512:
6
- metadata.gz: ce34b69d0059e0a5af071de68fe64340066b726c984c71f86213edb1ca6995d7250a97b28b3c081572325fd91af6e1dbb4418db310362bb87b7dbcb05a19d731
7
- data.tar.gz: 23abd33b373b5bbd6676a5a9ae10d6a1ae7c97b228ec182c250c81cae0c14d8bc8f173e4909c663883536fb4809baf257c9cb1661b6d8f0dbc971d7f141c9012
6
+ metadata.gz: dfc14d736db8c6d4f9c059d32ab0012c579c585a2b903ae2cf8920211be1ed95f5b42c07e4d56a710c8357df97d50fb7539ee8ea97d05bc030f2c8846adca79b
7
+ data.tar.gz: 12d82522d32fb0154b6e5e7c20464a2c01e47b4adb863a2475db3aa0ca8a584db430afb1ba84556240b8ab76398bf2b1a911f88d9b2196578f5a42137e7850a7
data/README.md CHANGED
@@ -3,11 +3,11 @@ RSpec API Matchers
3
3
 
4
4
  RSpecApi::Matchers lets you express outcomes on the response of web APIs.
5
5
 
6
- expect(404).to match_status(:not_found)
6
+ expect(response).to have_status(:not_found)
7
7
 
8
8
  More documentation and examples about RSpec Api are available at [http://rspec-api.github.io](http://rspec-api.github.io)
9
9
 
10
- [![Build Status](https://travis-ci.org/rspec-api/rspec-api-matchers.png)](https://travis-ci.org/rspec-api/rspec-api-matchers)
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)
@@ -21,7 +21,14 @@ Or install yourself by running `gem install rspec-api-matchers`.
21
21
  Available matchers
22
22
  ------------------
23
23
 
24
- expect(http_status_code).to match_status(http_status)
24
+ expect(response).to have_status(:ok)
25
+ expect(response).to include_content_type(:json)
26
+ expect(response).to have_prev_page_link
27
+ expect(response).to be_valid_json(Array)
28
+ expect(response).to be_a_jsonp('alert')
29
+ expect(response).to be_sorted(by: :id, verse: :desc)
30
+ expect(response).to be_filtered(10, by: :id)
31
+ expect(response).to have_attributes(id: {value: 1.2}, url: {type: {string: :url}})
25
32
 
26
33
  How to contribute
27
34
  =================
@@ -0,0 +1,24 @@
1
+ require 'rspec-api/matchers/jsonp'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response body is in JSONP format
9
+ #
10
+ # @note
11
+ #
12
+ # A JSONP should actually return application/javascript...
13
+ #
14
+ # @example
15
+ #
16
+ # # Passes if the body is a JSON wrapped in a callback
17
+ # body = 'alert([{"website":"http://www.example.com","flag":null}])'
18
+ # expect(OpenStruct.new body: body).to be_a_jsonp(:alert)
19
+ def be_a_jsonp(callback = nil)
20
+ RSpecApi::Matchers::Jsonp.new(callback)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'rspec-api/matchers/filter'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response body is filtered by +options[:by]+ comparing with +options[:comparing_with]+
9
+ #
10
+ # @example
11
+ #
12
+ # # Passes if the body only contains ID = 1
13
+ # body = '[{"id": 1}, {"id": 1}, {"id": 1}]'
14
+ # expect(OpenStruct.new body: body).to be_filtered(1, by: :id)
15
+ #
16
+ # # Passes if the body only contains ID < 10
17
+ # body = '[{"id": 1}, {"id": 6}, {"id": 3}]'
18
+ # expect(OpenStruct.new body: body).to be_filtered(10, by: :id, comparing_with: :<)
19
+ def be_filtered(value = nil, options = {})
20
+ RSpecApi::Matchers::Filter.new value, options
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'rspec-api/matchers/sort'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response body is sorted by +options[:by]+ with +options[:verse]+
9
+ #
10
+ # @example
11
+ #
12
+ # # Passes if the body is sorted by ascending IDs
13
+ # body = '[{"id": 1}, {"id": 2}, {"id": 3}]'
14
+ # expect(OpenStruct.new body: body).to be_sorted(by: :id)
15
+ #
16
+ # # Passes if the body is sorted by descending timestamps
17
+ # body = '[{"t": "2013-10-29T18:09:43Z"}, {"t": "2009-01-12T18:09:43Z"}]'
18
+ # expect(OpenStruct.new body: body).to be_sorted(by: :t, verse: :desc)
19
+ def be_sorted(options = {})
20
+ RSpecApi::Matchers::Sort.new options
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ require 'rspec-api/matchers/json'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response body is JSON. Optionally check if Array or Hash.
9
+ # Skips if if response body is JSON. Optionally check if Array or Hash.
10
+ #
11
+ # @example
12
+ #
13
+ # # Passes if the body is valid JSON
14
+ # body = '{"id": 1}'
15
+ # expect(OpenStruct.new body: body).to be_valid_json
16
+ #
17
+ # # Passes if the body is a valid JSON-marshalled Hash
18
+ # body = '{"id": 1}'
19
+ # expect(OpenStruct.new body: body).to be_valid_json(Hash)
20
+ #
21
+ # # Passes if the body is a valid JSON-marshalled Array
22
+ # body = '[{"id": 1}, {"id": 2}]'
23
+ # expect(OpenStruct.new body: body).to be_valid_json(Array)
24
+ def be_valid_json(type = nil)
25
+ RSpecApi::Matchers::Json.new(type)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ require 'rspec-api/matchers/attributes'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response body is an object or array of objects with +key+.
9
+ #
10
+ # @example
11
+ #
12
+ # # Passes if the body is an object with key :id
13
+ # body = '{"id": 1, "url": "http://www.example.com"}'
14
+ # expect(OpenStruct.new body: body).to have_attributes(:id)
15
+ #
16
+ # # Passes if the body is an object with key :id and value 1
17
+ # body = '{"id": 1, "url": "http://www.example.com"}'
18
+ # expect(OpenStruct.new body: body).to have_attributes(id: {value: 1})
19
+ #
20
+ # # Passes if the body is an array of objects, all with key :id
21
+ # body = '{"id": 1, "name": ""}, {"id": 2}]'
22
+ # expect(OpenStruct.new body: body).to have_attributes(:id)
23
+ #
24
+ # # Passes if the body is an array of object with key :id and odd values
25
+ # body = '{"id": 1, "name": ""}, {"id": 3}], {"id": 5}]'
26
+ # expect(OpenStruct.new body: body).to have_attributes(id: {value: -> v {v.odd?}})
27
+
28
+ # TODO: add &block options
29
+ def have_attributes(attributes = {})
30
+ RSpecApi::Matchers::Attributes.new attributes
31
+ end
32
+ # alias have_attribute have_attributes
33
+ # alias have_keys have_attributes
34
+ # alias have_key have_attributes
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec-api/matchers/prev_page_link'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response includes the pagination Link rel=prev in the headers
9
+ #
10
+ # @example
11
+ #
12
+ # # Passes if the headers specify the pagination link for Prev
13
+ # headers = {'Link' => '<https://example.com/1>; rel="prev"'}
14
+ # expect(OpenStruct.new headers: headers).to have_prev_page_link
15
+ def have_prev_page_link
16
+ RSpecApi::Matchers::PrevPageLink.new
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec-api/matchers/status'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module DSL
6
+ # Passes if response has the given HTTP status code.
7
+ #
8
+ # @example
9
+ #
10
+ # # Passes if the status is 200 OK (passed as a symbol)
11
+ # expect(OpenStruct.new status: 200).to match_status :ok
12
+ #
13
+ # # Passes if the status is 200 OK (passed as a number)
14
+ # expect(OpenStruct.new status: 200).to match_status 200
15
+ def have_status(status)
16
+ RSpecApi::Matchers::Status.new status
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ require 'rspec-api/matchers/content_type'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
6
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
7
+ module DSL
8
+ # Passes if response includes the given Content-Type JSON in the headers
9
+ #
10
+ # @example
11
+ #
12
+ # # Passes if the headers specify that Content-Type is JSON
13
+ # headers ={'Content-Type' => 'application/json; charset=utf-8'}
14
+ # expect(OpenStruct.new headers: headers).to include_content_type(:json)
15
+ def include_content_type(type = nil)
16
+ content_type = case type
17
+ when :json then 'application/json; charset=utf-8'
18
+ end
19
+ RSpecApi::Matchers::ContentType.new(content_type)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'rspec-api/matchers/run_if'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ module DSL
6
+ # Creates a +method_name+_if method for each DSL method
7
+ #
8
+ # @example
9
+ #
10
+ # def have_prev_page_link_if(condition, *args)
11
+ # RSpecApi::Matchers::RunIf.new condition, have_prev_page_link(*args)
12
+ # end
13
+ RSpecApi::Matchers::DSL.instance_methods.each do |method|
14
+ define_method("#{method}_if") do |condition, *args|
15
+ run_if condition, send(method, *args)
16
+ end
17
+ end
18
+
19
+ def run_if(run, matcher)
20
+ RSpecApi::Matchers::RunIf.new run, matcher
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,28 @@
1
- require 'rspec-api/matchers/version'
2
- require 'rspec-api/matchers/matchers'
3
- require 'rspec-api/matchers/match_status'
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_valid_json'
7
+ require 'rspec-api/dsl/be_filtered'
8
+ require 'rspec-api/dsl/have_attributes'
9
+ require 'rspec-api/dsl/run_if' # should be the last, for metaprogramming
10
+
11
+ require 'rspec/matchers'
12
+
13
+ # Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
14
+ # makes RSpecApi::Matchers::Status available as expect(...).to match_status
15
+
16
+ module RSpecApi
17
+ module Matchers
18
+ module DSL
19
+ end
20
+ end
21
+ end
22
+
23
+ module RSpec
24
+ module Matchers
25
+ # Make RSpecApi::Matchers::DSL methods available inside RSpec
26
+ include ::RSpecApi::Matchers::DSL
27
+ end
28
+ end
@@ -0,0 +1,171 @@
1
+ require 'json'
2
+ require 'active_support/core_ext/array/wrap'
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'active_support/core_ext/array/conversions'
5
+
6
+ module RSpecApi
7
+ module Matchers
8
+ class Attributes
9
+ def initialize(attributes = {})
10
+ @attributes = as_hash(attributes)
11
+ end
12
+
13
+ def matches?(response)
14
+ @body = response.body
15
+ json = extract_symbolized_json_from @body
16
+ # NOTE: Might add this... but might be too strict. For instance, if I
17
+ # ask for ?page=2, I might get an empty array. Maybe in that case I
18
+ # should not even check for attributes? Maybe it can be another best
19
+ # practice: adding query params will not affect the attributes (so I
20
+ # can just check them when there are no query params)... of course
21
+ # unless the query params is ?fields=id,name
22
+ # if json.is_a?(Array) and json.empty?
23
+ # raise RSpec::Core::Pending::PendingDeclaredInExample.new "You are testing if an array is sorted, but the array is empty. Try with more fixtures"
24
+ # end
25
+ has_attributes? json, @attributes
26
+ rescue JSON::ParserError, JSON::GeneratorError
27
+ false
28
+ end
29
+ alias == matches?
30
+
31
+ def failure_message_for_should
32
+ "expected body to #{description}, but got #{@body}"
33
+ end
34
+
35
+ def failure_message_for_should_not
36
+ "expected body not to #{description}, but it did"
37
+ end
38
+
39
+ def description
40
+ desc = @attributes.map do |name, options|
41
+ text = "#{name}"
42
+ if options.key?(:value)
43
+ text << "="
44
+ text << case options[:value]
45
+ when NilClass then 'nil'
46
+ when Proc then '[Proc value]'
47
+ else "#{options[:value]}"
48
+ end
49
+ end
50
+ if options.key?(:type)
51
+ expected_types = Array.wrap(options.fetch :type, :any)
52
+ types = expected_types.map do |type|
53
+ case type
54
+ when Hash
55
+ type.map do |k, format|
56
+ case format
57
+ when Hash, Array
58
+ "#{k} with attributes"
59
+ else # Symbol
60
+ "#{format} #{k}"
61
+ end
62
+ end.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
63
+ else "#{type}"
64
+ end
65
+ end
66
+ text << " (#{types.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')})" if types.any?
67
+ end
68
+ text
69
+ end.to_sentence
70
+ ['have attributes', desc].join(' ').strip
71
+ end
72
+
73
+ private
74
+
75
+ def has_attributes?(item_or_items, attributes)
76
+ attributes.deep_symbolize_keys!
77
+ items = Array.wrap item_or_items
78
+ attributes.all? do |name, options|
79
+ has_attribute? items, name, options
80
+ end
81
+ end
82
+
83
+ def has_attribute?(items, name, options)
84
+ if items.all?{|item| has_key? item, name}
85
+ values = items.map{|item| item[name]}
86
+ expected_types = Array.wrap(options.fetch :type, :any)
87
+ expected_value = options.fetch :value, :any
88
+ values.all? do |value|
89
+ matches_value?(value, expected_value) && matches_types?(value, expected_types)
90
+ end
91
+ end
92
+ end
93
+
94
+ def has_key?(item, name)
95
+ item.key? name
96
+ end
97
+
98
+
99
+ def matches_value?(value, expected_value)
100
+ case expected_value
101
+ when :any then true
102
+ when Proc then expected_value.call value
103
+ else value == expected_value
104
+ end
105
+ end
106
+
107
+ def matches_types?(value, expected_types)
108
+ expected_types.any? do |type|
109
+ matches_type_and_format? value, type
110
+ end
111
+ end
112
+
113
+ def matches_type_and_format?(value, type)
114
+ type = Hash[type, :any] unless type.is_a?(Hash)
115
+ type.any? do |type, format|
116
+ matches_type?(value, type) && matches_format?(value, format)
117
+ end
118
+ end
119
+
120
+ def matches_type?(value, type)
121
+ type_to_classes(type).any?{|klass| value.is_a? klass}
122
+ end
123
+
124
+ def matches_format?(value, format)
125
+ case format
126
+ when :url then value =~ URI::regexp
127
+ when :integer then value.is_a? Integer
128
+ when :timestamp then DateTime.iso8601 value rescue false
129
+ when :email then value =~ %r{(?<name>.+?)@(?<host>.+?)\.(?<domain>.+?)}
130
+ when Hash, Array then value.any? ?has_attributes?(value, as_hash(format)) : true
131
+ when String then matches_format?(value, format.to_sym)
132
+ when :any then true
133
+ end
134
+ end
135
+
136
+ def type_to_classes(type)
137
+ Array.wrap case type
138
+ when :string then String
139
+ when :array then Array
140
+ when :object then Hash
141
+ when :null then NilClass
142
+ when :boolean then [TrueClass, FalseClass]
143
+ when :number then Numeric
144
+ when String then type_to_classes(type.to_sym)
145
+ when :any then Object
146
+ end
147
+ end
148
+
149
+ # These go elsewhere
150
+
151
+ def as_hash(anything)
152
+ if anything.is_a? Hash
153
+ anything
154
+ elsif anything.nil?
155
+ {}
156
+ else
157
+ Hash[*Array.wrap(anything).map{|x| x.is_a?(Hash) ? [x.keys.first, x.values.first] : [x, {}]}.flatten]
158
+ end
159
+ end
160
+
161
+ def extract_symbolized_json_from(something)
162
+ JSON without_callbacks(something), symbolize_names: true
163
+ end
164
+
165
+ def without_callbacks(something)
166
+ callback_pattern = %r[^.+?\((.*?)\)$]
167
+ something =~ callback_pattern ? something.match(callback_pattern)[1] : something
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,30 @@
1
+ module RSpecApi
2
+ module Matchers
3
+ class ContentType
4
+ def initialize(content_type)
5
+ @content_type = content_type
6
+ end
7
+
8
+ def matches?(response)
9
+ @headers = response.headers
10
+ @headers['Content-Type'] == @content_type if @headers.key? 'Content-Type'
11
+ end
12
+
13
+ def failure_message_for_should
14
+ "expected headers to #{description}, but got #{@headers}"
15
+ end
16
+
17
+ def failure_message_for_should_not
18
+ "expected headers not to #{description}, but got #{@headers}"
19
+ end
20
+
21
+ def description
22
+ %Q{include 'Content-Type': '#{@content_type}'}
23
+ end
24
+
25
+ def description_for_run_if
26
+ %Q{include any specific 'Content-Type'}
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ require 'json'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ class Filter
6
+ def initialize(value, options = {})
7
+ @value = value
8
+ @field = options[:by]
9
+ @comparing_with = options.fetch :comparing_with, Proc.new{|x,y| x == y}
10
+ end
11
+
12
+ def matches?(response)
13
+ @desc = " by #{@field}#{@comparing_with}#{@value}"
14
+ @body = response.body
15
+ array = extract_json_from @body
16
+ array.all? do |item| # TODO: Don't always use string
17
+ @comparing_with.call @value, item[@field.to_s].to_s
18
+ end
19
+ end
20
+ alias == matches?
21
+
22
+ def failure_message_for_should
23
+ "expected body to #{description}, but got #{@body}"
24
+ end
25
+
26
+ def failure_message_for_should_not
27
+ "expected body not to #{description}, but it was"
28
+ end
29
+
30
+ def description
31
+ %Q(be filtered#{@desc})
32
+ end
33
+
34
+ private
35
+
36
+ # These go elsewhere
37
+
38
+ def extract_json_from(something)
39
+ array = JSON without_callbacks(something)
40
+ if array.is_a?(Array) and array.empty?
41
+ raise RSpec::Core::Pending::PendingDeclaredInExample.new "You are testing if an array is sorted, but the array is empty. Try with more fixtures"
42
+ end
43
+ array
44
+ rescue JSON::ParserError, JSON::GeneratorError
45
+ nil
46
+ end
47
+
48
+ def without_callbacks(something)
49
+ callback_pattern = %r[^.+?\((.*?)\)$]
50
+ something =~ callback_pattern ? something.match(callback_pattern)[1] : something
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ require 'json'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ class Json
6
+ def initialize(type)
7
+ @type = type
8
+ @desc = " #{@type}" if @type
9
+ end
10
+
11
+ def matches?(response)
12
+ @body = response.body
13
+ json = parse_json_of @body
14
+ @type ? json.is_a?(@type) : true
15
+ end
16
+ alias == matches?
17
+
18
+ def failure_message_for_should
19
+ "expected body to #{description}, but got #{@body}"
20
+ end
21
+
22
+ def failure_message_for_should_not
23
+ "expected body not to #{description}, but it was"
24
+ end
25
+
26
+ def description
27
+ %Q(be a valid JSON#{@desc})
28
+ end
29
+
30
+ private
31
+
32
+ # These go elsewhere
33
+
34
+ def parse_json_of(something)
35
+ JSON without_callbacks(something)
36
+ rescue JSON::ParserError, JSON::GeneratorError
37
+ nil
38
+ end
39
+
40
+ def without_callbacks(something)
41
+ callback_pattern = %r[^.+?\((.*?)\)$]
42
+ something =~ callback_pattern ? something.match(callback_pattern)[1] : something
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ module RSpecApi
2
+ module Matchers
3
+ class Jsonp
4
+ def initialize(callback)
5
+ @callback = callback
6
+ end
7
+
8
+ def matches?(response)
9
+ @body = response.body
10
+ @body =~ %r{^#{@callback || '.+?'}\((.*?)\)$}
11
+ end
12
+ alias == matches?
13
+
14
+ def failure_message_for_should
15
+ "expected body to #{description}, but got #{@body}"
16
+ end
17
+
18
+ def failure_message_for_should_not
19
+ "expected body not to #{description}, but it was"
20
+ end
21
+
22
+ def description
23
+ %Q(be wrapped in a JSONP callback #{@callback}).rstrip
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module RSpecApi
2
+ module Matchers
3
+ class PrevPageLink
4
+ def matches?(response)
5
+ @headers = response.headers
6
+ links = @headers['Link'] || '' # not fetch, see http://git.io/CUz3-Q
7
+ links =~ %r{<.+?>. rel\="prev"}
8
+ end
9
+
10
+ def failure_message_for_should
11
+ "expected headers to #{description}, but got #{@headers}"
12
+ end
13
+
14
+ def failure_message_for_should_not
15
+ "expected headers not to #{description}, but got #{@headers}"
16
+ end
17
+
18
+ def description
19
+ %Q{include a 'Link' to the previous page}
20
+ end
21
+
22
+ def description_for_run_if
23
+ %Q{include any specific pagination 'Link'}
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ module RSpecApi
2
+ module Matchers
3
+ class RunIf
4
+ def initialize(run, matcher)
5
+ @run = run
6
+ @matcher = matcher
7
+ end
8
+
9
+ def matches?(response)
10
+ !@run || @matcher.matches?(response)
11
+ end
12
+ alias == matches?
13
+
14
+ def does_not_match?(response)
15
+ !@run || !matches?(response)
16
+ end
17
+
18
+ def failure_message_for_should
19
+ @matcher.failure_message_for_should
20
+ end
21
+
22
+ def failure_message_for_should_not
23
+ @matcher.failure_message_for_should_not
24
+ end
25
+
26
+ def description
27
+ if @run
28
+ @matcher.description
29
+ else
30
+ if @matcher.respond_to? :description_for_run_if
31
+ %Q{not be expected to #{@matcher.description_for_run_if}}
32
+ else
33
+ %Q{not be expected to #{@matcher.description}}
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,65 @@
1
+ require 'json'
2
+
3
+ module RSpecApi
4
+ module Matchers
5
+ class Sort
6
+ def initialize(options = {})
7
+ @field = options[:by]
8
+ @reverse = options[:verse].to_s == 'desc' || options[:verse].to_s == 'descending' || options[:reverse] == true || options[:ascending] == true || options[:asc] == true || options[:descending] == false || options[:desc] == false
9
+ end
10
+
11
+ def matches?(response)
12
+ # If we don't get which field the body should be sorted by, then we
13
+ # say that it's sorted. For instance sort by random... no expectations
14
+ # We might still want to do some check about being a JSON array, though
15
+ if @field.nil?
16
+ true
17
+ else
18
+ @desc = " by #{'descending ' if @reverse}#{@field}"
19
+ @body = response.body
20
+ array = extract_array of: @field, from: @body # what if this fails?
21
+ is_sorted? array, @reverse
22
+ end
23
+ end
24
+ alias == matches?
25
+
26
+ def failure_message_for_should
27
+ # NOTE: might just print the (unsorted) fields, not the whole @body
28
+ "expected body to #{description}, but got #{@body}"
29
+ end
30
+
31
+ def failure_message_for_should_not
32
+ "expected body not to #{description}, but it was"
33
+ end
34
+
35
+ def description
36
+ %Q(be sorted#{@desc})
37
+ end
38
+
39
+ private
40
+
41
+ def is_sorted?(array, reverse)
42
+ return false unless array.is_a?(Array)
43
+ array.reverse! if reverse
44
+ array == array.sort
45
+ end
46
+
47
+ # These go elsewhere
48
+
49
+ def extract_array(options = {})
50
+ array = JSON without_callbacks(options[:from])
51
+ if array.is_a?(Array) and array.empty?
52
+ raise RSpec::Core::Pending::PendingDeclaredInExample.new "You are testing if an array is sorted, but the array is empty. Try with more fixtures"
53
+ end
54
+ array.map{|item| item[options[:of].to_s]} # what if it's not an array of hashes?
55
+ rescue JSON::ParserError, JSON::GeneratorError
56
+ nil
57
+ end
58
+
59
+ def without_callbacks(something)
60
+ callback_pattern = %r[^.+?\((.*?)\)$]
61
+ something =~ callback_pattern ? something.match(callback_pattern)[1] : something
62
+ end
63
+ end
64
+ end
65
+ end
@@ -2,27 +2,27 @@ require 'rack/utils'
2
2
 
3
3
  module RSpecApi
4
4
  module Matchers
5
- class MatchStatus
6
- def initialize(expected_status)
7
- @expected_status = expected_status
5
+ class Status
6
+ def initialize(status)
7
+ @expected_status = status
8
8
  end
9
9
 
10
- def matches?(actual_code)
11
- @actual_code = actual_code
12
- actual_code == expected_code
10
+ def matches?(response)
11
+ @status = response.status
12
+ @status == expected_code
13
13
  end
14
14
  alias == matches?
15
15
 
16
16
  def failure_message_for_should
17
- "expected #{@actual_code} to #{description}"
17
+ "expected HTTP status code #{expected_code}, got #{@status}"
18
18
  end
19
19
 
20
20
  def failure_message_for_should_not
21
- "expected #{@actual_code} not to #{description}"
21
+ "expected HTTP status code not to be #{expected_code}, but it was"
22
22
  end
23
23
 
24
24
  def description
25
- "be #{@expected_status}"
25
+ "be HTTP status code #{expected_code}"
26
26
  end
27
27
 
28
28
  private
@@ -36,7 +36,7 @@ module RSpecApi
36
36
  # @example
37
37
  # status_to_code(:ok) # => 200
38
38
  # status_to_code(200) # => 200
39
- # status_to_code(987) # => raise
39
+ # status_to_code(987) # => raise ArgumentError
40
40
  def status_to_numeric_code(status)
41
41
  code = status.is_a?(Symbol) ? Rack::Utils.status_code(status) : status
42
42
  validate_status_code! code
@@ -47,7 +47,7 @@ module RSpecApi
47
47
  #
48
48
  # @example
49
49
  # validate_status_code!(200) # => (no error)
50
- # validate_status_code!(999) # => raise
50
+ # validate_status_code!(999) # => raise ArgumentError
51
51
  def validate_status_code!(code)
52
52
  valid_codes = Rack::Utils::SYMBOL_TO_STATUS_CODE.values
53
53
  message = "#{code} must be a valid HTTP status code: #{valid_codes}"
@@ -1,5 +1,5 @@
1
1
  module RSpecApi
2
2
  module Matchers
3
- VERSION = '0.0.1'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-api-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-29 00:00:00.000000000 Z
11
+ date: 2013-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -87,8 +101,24 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
90
- - lib/rspec-api/matchers/match_status.rb
91
- - lib/rspec-api/matchers/matchers.rb
104
+ - lib/rspec-api/dsl/be_a_jsonp.rb
105
+ - lib/rspec-api/dsl/be_filtered.rb
106
+ - lib/rspec-api/dsl/be_sorted.rb
107
+ - lib/rspec-api/dsl/be_valid_json.rb
108
+ - lib/rspec-api/dsl/have_attributes.rb
109
+ - lib/rspec-api/dsl/have_prev_page_link.rb
110
+ - lib/rspec-api/dsl/have_status.rb
111
+ - lib/rspec-api/dsl/include_content_type.rb
112
+ - lib/rspec-api/dsl/run_if.rb
113
+ - lib/rspec-api/matchers/attributes.rb
114
+ - lib/rspec-api/matchers/content_type.rb
115
+ - lib/rspec-api/matchers/filter.rb
116
+ - lib/rspec-api/matchers/json.rb
117
+ - lib/rspec-api/matchers/jsonp.rb
118
+ - lib/rspec-api/matchers/prev_page_link.rb
119
+ - lib/rspec-api/matchers/run_if.rb
120
+ - lib/rspec-api/matchers/sort.rb
121
+ - lib/rspec-api/matchers/status.rb
92
122
  - lib/rspec-api/matchers/version.rb
93
123
  - lib/rspec-api/matchers.rb
94
124
  - lib/rspec-api-matchers.rb
@@ -1,26 +0,0 @@
1
- require 'rspec/matchers'
2
-
3
- module RSpecApi
4
- module Matchers
5
- # Passes if receiver is the same HTTP status code as the argument.
6
- # The receiver can either be in a symbolic or numeric form.
7
- #
8
- # @example
9
- #
10
- # # Passes if 200 corresponds to 200
11
- # expect(200).to match_status(200)
12
- #
13
- # # Passes if :ok corresponds to :ok
14
- # expect(:ok).to match_status(200)
15
- def match_status(expected_status)
16
- RSpecApi::Matchers::MatchStatus.new(expected_status)
17
- end
18
- end
19
- end
20
-
21
- module RSpec
22
- module Matchers
23
- # Make RSpecApi::Matchers available inside RSpec
24
- include ::RSpecApi::Matchers
25
- end
26
- end