rspec-api-matchers 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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