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
@@ -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