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.
- checksums.yaml +4 -4
- data/README.md +24 -19
- data/lib/rspec-api/matchers.rb +38 -21
- data/lib/rspec-api/matchers/attributes/have_attributes.rb +23 -0
- data/lib/rspec-api/matchers/attributes/matcher.rb +101 -0
- data/lib/rspec-api/matchers/collection/be_a_collection.rb +20 -0
- data/lib/rspec-api/matchers/collection/matcher.rb +19 -0
- data/lib/rspec-api/matchers/content_type/have_content_type.rb +24 -0
- data/lib/rspec-api/matchers/content_type/matcher.rb +23 -0
- data/lib/rspec-api/matchers/filter/be_filtered.rb +20 -0
- data/lib/rspec-api/matchers/filter/matcher.rb +57 -0
- data/lib/rspec-api/matchers/headers/have_headers.rb +20 -0
- data/lib/rspec-api/matchers/headers/matcher.rb +28 -0
- data/lib/rspec-api/matchers/json/be_valid_json.rb +20 -0
- data/lib/rspec-api/matchers/json/matcher.rb +41 -0
- data/lib/rspec-api/matchers/jsonp/be_wrapped_in_callback.rb +24 -0
- data/lib/rspec-api/matchers/jsonp/matcher.rb +23 -0
- data/lib/rspec-api/matchers/page_links/have_page_links.rb +25 -0
- data/lib/rspec-api/matchers/page_links/matcher.rb +18 -0
- data/lib/rspec-api/matchers/response/be_a_valid_response.rb +20 -0
- data/lib/rspec-api/matchers/response/matcher.rb +52 -0
- data/lib/rspec-api/matchers/sort/be_sorted.rb +20 -0
- data/lib/rspec-api/matchers/sort/matcher.rb +56 -0
- data/lib/rspec-api/matchers/status/have_status.rb +25 -0
- data/lib/rspec-api/matchers/status/matcher.rb +46 -0
- data/lib/rspec-api/matchers/version.rb +1 -1
- metadata +24 -18
- data/lib/rspec-api/dsl/be_a_collection.rb +0 -24
- data/lib/rspec-api/dsl/be_a_jsonp.rb +0 -24
- data/lib/rspec-api/dsl/be_filtered.rb +0 -24
- data/lib/rspec-api/dsl/be_sorted.rb +0 -24
- data/lib/rspec-api/dsl/have_attributes.rb +0 -37
- data/lib/rspec-api/dsl/have_prev_page_link.rb +0 -20
- data/lib/rspec-api/dsl/have_status.rb +0 -20
- data/lib/rspec-api/dsl/include_content_type.rb +0 -24
- data/lib/rspec-api/matchers/attributes.rb +0 -171
- data/lib/rspec-api/matchers/collection.rb +0 -42
- data/lib/rspec-api/matchers/content_type.rb +0 -26
- data/lib/rspec-api/matchers/filter.rb +0 -58
- data/lib/rspec-api/matchers/jsonp.rb +0 -27
- data/lib/rspec-api/matchers/prev_page_link.rb +0 -23
- data/lib/rspec-api/matchers/sort.rb +0 -65
- data/lib/rspec-api/matchers/status.rb +0 -60
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'rspec-api/matchers/collection'
|
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 a collection
|
9
|
-
#
|
10
|
-
# @example
|
11
|
-
#
|
12
|
-
# # Passes if the body is a collection
|
13
|
-
# body = '[{"id": 1}]'
|
14
|
-
# expect(OpenStruct.new body: body).to be_a_collection
|
15
|
-
#
|
16
|
-
# # Passes if the body is not a collection
|
17
|
-
# body = '{"id": 1}'
|
18
|
-
# expect(OpenStruct.new body: body).not_to be_a_collection
|
19
|
-
def be_a_collection
|
20
|
-
RSpecApi::Matchers::Collection.new
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,24 +0,0 @@
|
|
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
|
@@ -1,24 +0,0 @@
|
|
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
|
@@ -1,24 +0,0 @@
|
|
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
|
@@ -1,37 +0,0 @@
|
|
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
|
@@ -1,20 +0,0 @@
|
|
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
|
@@ -1,20 +0,0 @@
|
|
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
|
@@ -1,24 +0,0 @@
|
|
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
|
-
when :any then %r{}
|
19
|
-
end
|
20
|
-
RSpecApi::Matchers::ContentType.new(content_type)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,171 +0,0 @@
|
|
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 has attributes, 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
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
|
-
module RSpecApi
|
4
|
-
module Matchers
|
5
|
-
class Collection
|
6
|
-
|
7
|
-
def matches?(response)
|
8
|
-
@body = response.body
|
9
|
-
json = parse_json_of @body
|
10
|
-
json.is_a?(Array)
|
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 got #{@body}"
|
20
|
-
end
|
21
|
-
|
22
|
-
def description
|
23
|
-
%Q(be a collection)
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
# These go elsewhere
|
29
|
-
|
30
|
-
def parse_json_of(something)
|
31
|
-
JSON without_callbacks(something)
|
32
|
-
rescue JSON::ParserError, JSON::GeneratorError
|
33
|
-
nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def without_callbacks(something)
|
37
|
-
callback_pattern = %r[^.+?\((.*?)\)$]
|
38
|
-
something =~ callback_pattern ? something.match(callback_pattern)[1] : something
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,26 +0,0 @@
|
|
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
|
-
@content_type.match @headers['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
|
-
end
|
25
|
-
end
|
26
|
-
end
|