rspec-solr 1.0.1 → 2.0.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.
@@ -1,28 +1,19 @@
1
1
  # Custom RSpec Matchers for Solr responses
2
2
  module RSpecSolr::Matchers
3
-
4
- # Determine if the receiver has Solr documents
5
- # NOTE: this is about the TOTAL number of Solr documents matching the query, not solely the number of docs in THIS response
6
- def have_documents
7
- # Placeholder method for documentation purposes;
8
- # the actual method is defined using RSpec's matcher DSL
9
- end
10
-
11
- # Define .have_documents
3
+ # @!method have_documents
12
4
  # Determine if the receiver (a Solr response object) has at least one document
13
5
  # NOTE: this is about the TOTAL number of Solr documents matching the query, not solely the number of docs in THIS response
14
- RSpec::Matchers.define :have_documents do
6
+ RSpec::Matchers.define :have_documents do
15
7
  match do |solr_resp|
16
- solr_resp["response"]["numFound"] > 0
8
+ solr_resp['response']['numFound'] > 0
9
+ end
10
+
11
+ failure_message do |solr_resp|
12
+ "expected documents in Solr response #{solr_resp['response']}"
17
13
  end
18
14
 
19
- failure_message_for_should do |solr_resp|
20
- "expected documents in Solr response #{solr_resp["response"]}"
15
+ failure_message_when_negated do |solr_resp|
16
+ "did not expect documents, but Solr response had #{solr_resp['response']['numFound']}"
21
17
  end
22
-
23
- failure_message_for_should_not do |solr_resp|
24
- "did not expect documents, but Solr response had #{solr_resp["response"]["numFound"]}"
25
- end
26
18
  end
27
-
28
- end
19
+ end
@@ -1,26 +1,18 @@
1
1
  # Custom RSpec Matchers for Solr responses
2
2
  module RSpecSolr::Matchers
3
-
4
- # Determine if the receiver has the facet_field
5
- def have_facet_field
6
- # Placeholder method for documentation purposes;
7
- # the actual method is defined using RSpec's matcher DSL
8
- end
9
-
10
3
  # this is the lambda used to determine if the receiver (a Solr response object) has non-empty values for the facet field
11
4
  # as the expected RSpecSolr::SolrResponseHash
12
- def self.have_facet_field_body
13
- lambda { |expected_facet_field_name|
14
-
15
- match_for_should do |solr_resp|
5
+ def self.facet_field_body?
6
+ lambda do |expected_facet_field_name|
7
+ match do |solr_resp|
16
8
  if @facet_val
17
9
  solr_resp.has_facet_field_with_value?(expected_facet_field_name, @facet_val)
18
10
  else
19
11
  solr_resp.has_facet_field?(expected_facet_field_name)
20
12
  end
21
13
  end
22
-
23
- match_for_should_not do |solr_resp|
14
+
15
+ match_when_negated do |solr_resp|
24
16
  # we should fail if we are looking for a specific facet value but the facet field isn't present in the response
25
17
  @has_field = solr_resp.has_facet_field?(expected_facet_field_name)
26
18
  if @facet_val
@@ -34,15 +26,15 @@ module RSpecSolr::Matchers
34
26
  end
35
27
  end
36
28
 
37
- failure_message_for_should do |solr_resp|
29
+ failure_message do |solr_resp|
38
30
  if @facet_val
39
31
  "expected facet field #{expected_facet_field_name} with value #{@facet_val} in Solr response: #{solr_resp}"
40
32
  else
41
33
  "expected facet field #{expected_facet_field_name} with values in Solr response: #{solr_resp}"
42
34
  end
43
35
  end
44
-
45
- failure_message_for_should_not do |solr_resp|
36
+
37
+ failure_message_when_negated do |solr_resp|
46
38
  if @facet_val
47
39
  if @has_field
48
40
  "expected facet field #{expected_facet_field_name} not to have value #{@facet_val} in Solr response: #{solr_resp}"
@@ -52,18 +44,18 @@ module RSpecSolr::Matchers
52
44
  else
53
45
  "expected no #{expected_facet_field_name} facet field in Solr response: #{solr_resp}"
54
46
  end
55
- end
56
-
47
+ end
48
+
57
49
  chain :with_value do |val|
58
50
  @facet_val = val
59
51
  end
60
-
52
+
61
53
  diffable
62
- }
54
+ end
63
55
  end
64
-
56
+
57
+ # @!method have_facet_field
65
58
  # Determine if the receiver (a Solr response object) has the same number of total documents as the expected RSpecSolr::SolrResponseHash
66
59
  # NOTE: this is about the TOTAL number of Solr documents matching the queries, not solely the number of docs in THESE responses
67
- RSpec::Matchers.define :have_facet_field, &have_facet_field_body
68
-
69
- end
60
+ RSpec::Matchers.define :have_facet_field, &facet_field_body?
61
+ end
@@ -3,26 +3,25 @@
3
3
  module RSpec
4
4
  module Matchers
5
5
  module BuiltIn
6
- class Include
7
-
6
+ class Include
8
7
  # chain method for .should include().in_first(n)
9
8
  # sets @max_doc_position for use in perform_match method
10
9
  # @return self - "each method must return self in order to chain methods together"
11
- def in_first(num=1)
10
+ def in_first(num = 1)
12
11
  @max_doc_position = num
13
12
  self
14
13
  end
15
-
14
+
16
15
  alias_method :as_first, :in_first
17
-
16
+
18
17
  # chain method for .should include().in_each_of_first(n)
19
18
  # sets @min_for_last_matching_doc_ix for use in perform_match method
20
19
  # @return self - "each method must return self in order to chain methods together"
21
- def in_each_of_first(num=1)
20
+ def in_each_of_first(num = 1)
22
21
  @min_for_last_matching_doc_pos = num
23
22
  self
24
23
  end
25
-
24
+
26
25
  # chain method for .should include().before()
27
26
  # sets @before_expected for use in perform_match method
28
27
  # @return self - "each method must return self in order to chain methods together"
@@ -30,46 +29,51 @@ module RSpec
30
29
  # get first doc position by calling has_document ???
31
30
  @before_expected = expected
32
31
  self
33
- end
32
+ end
34
33
 
35
34
  # override failure message for improved readability
36
- def failure_message_for_should
35
+ def failure_message
37
36
  assert_ivars :@actual, :@expected
38
- # FIXME: DRY up these messages across cases and across should and should_not
37
+ name_to_sentence = 'include'
38
+ # FIXME: DRY up these messages across cases and across should and should_not
39
39
  if @before_expected
40
- "expected response to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} before #{doc_label_str(@before_expected)} matching #{@before_expected.inspect}: #{@actual.inspect} "
40
+ "expected response to #{name_to_sentence} #{doc_label_str(@expected)}#{to_sentence(@expected)} before #{doc_label_str(@before_expected)} matching #{@before_expected.inspect}: #{@actual.inspect} "
41
41
  elsif @min_for_last_matching_doc_pos
42
- "expected each of the first #{@min_for_last_matching_doc_pos.to_s} documents to #{name_to_sentence}#{expected_to_sentence} in response: #{@actual.inspect}"
42
+ "expected each of the first #{@min_for_last_matching_doc_pos} documents to #{name_to_sentence}#{to_sentence(@expected)} in response: #{@actual.inspect}"
43
43
  elsif @max_doc_position
44
- "expected response to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} in first #{@max_doc_position.to_s} results: #{@actual.inspect}"
44
+ "expected response to #{name_to_sentence} #{doc_label_str(@expected)}#{to_sentence(@expected)} in first #{@max_doc_position} results: #{@actual.inspect}"
45
45
  else
46
46
  super
47
47
  end
48
48
  end
49
-
49
+
50
50
  # override failure message for improved readability
51
- def failure_message_for_should_not
51
+ def failure_message_when_negated
52
52
  assert_ivars :@actual, :@expected
53
+ name_to_sentence = 'include'
53
54
  if @before_expected
54
- "expected response not to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} before #{doc_label_str(@before_expected)} matching #{@before_expected.inspect}: #{@actual.inspect} "
55
+ "expected response not to #{name_to_sentence} #{doc_label_str(@expected)}#{to_sentence(@expected)} before #{doc_label_str(@before_expected)} matching #{@before_expected.inspect}: #{@actual.inspect} "
55
56
  elsif @min_for_last_matching_doc_pos
56
- "expected some of the first #{@min_for_last_matching_doc_pos.to_s} documents not to #{name_to_sentence}#{expected_to_sentence} in response: #{@actual.inspect}"
57
+ "expected some of the first #{@min_for_last_matching_doc_pos} documents not to #{name_to_sentence}#{to_sentence(@expected)} in response: #{@actual.inspect}"
57
58
  elsif @max_doc_position
58
- "expected response not to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} in first #{@max_doc_position.to_s} results: #{@actual.inspect}"
59
+ "expected response not to #{name_to_sentence} #{doc_label_str(@expected)}#{to_sentence(@expected)} in first #{@max_doc_position} results: #{@actual.inspect}"
59
60
  else
60
61
  super
61
62
  end
62
63
  end
63
-
64
64
 
65
- private
65
+ private
66
+
66
67
  # overriding method so we can use RSpec include matcher for document in Solr response
67
68
  # my_solr_resp_hash.should include({"id" => "666"})
68
- def perform_match(predicate, hash_predicate, actuals, expecteds)
69
- expecteds.send(predicate) do |expected|
70
- if comparing_doc_to_solr_resp_hash?(actuals, expected)
69
+
70
+ def excluded_from_actual
71
+ return [] unless @actual.respond_to?(:include?)
72
+
73
+ expected.each_with_object([]) do |expected_item, memo|
74
+ if comparing_doc_to_solr_resp_hash?(expected_item)
71
75
  if @before_expected
72
- before_ix = actuals.get_first_doc_index(@before_expected)
76
+ before_ix = actual.get_first_doc_index(@before_expected)
73
77
  if before_ix
74
78
  @max_doc_position = before_ix + 1
75
79
  else
@@ -77,28 +81,30 @@ private
77
81
  @max_doc_position = -1
78
82
  end
79
83
  end
80
- if @min_for_last_matching_doc_pos
81
- actuals.has_document?(expected, @min_for_last_matching_doc_pos, true)
84
+ if @min_for_last_matching_doc_pos
85
+ memo << expected_item unless yield actual.has_document?(expected_item, @min_for_last_matching_doc_pos, true)
82
86
  else
83
- actuals.has_document?(expected, @max_doc_position)
87
+ memo << expected_item unless yield actual.has_document?(expected_item, @max_doc_position)
84
88
  end
85
- elsif comparing_hash_values?(actuals, expected)
86
- expected.send(hash_predicate) {|k,v| actuals[k] == v}
87
- elsif comparing_hash_keys?(actuals, expected)
88
- actuals.has_key?(expected)
89
+ elsif comparing_hash_to_a_subset?(expected_item)
90
+ expected_item.each do |(key, value)|
91
+ memo << { key => value } unless yield actual_hash_includes?(key, value)
92
+ end
93
+ elsif comparing_hash_keys?(expected_item)
94
+ memo << expected_item unless yield actual_hash_has_key?(expected_item)
89
95
  else
90
- actuals.include?(expected)
96
+ memo << expected_item unless yield actual_collection_includes?(expected_item)
91
97
  end
92
98
  end
93
99
  end
94
-
95
- # is actual param a SolrResponseHash?
96
- def comparing_doc_to_solr_resp_hash?(actual, expected)
97
- actual.is_a?(RSpecSolr::SolrResponseHash)
100
+
101
+ # is actual param a SolrResponseHash?
102
+ def comparing_doc_to_solr_resp_hash?(expected_item)
103
+ actual.is_a?(RSpecSolr::SolrResponseHash) && !expected_item.is_a?(RSpecSolr::SolrResponseHash)
98
104
  end
99
-
105
+
100
106
  def method_missing(method, *args, &block)
101
- if (method =~ /documents?/ || method =~ /results?/)
107
+ if method =~ /documents?/ || method =~ /results?/
102
108
  @collection_name = method
103
109
  @args = args
104
110
  @block = block
@@ -107,19 +113,32 @@ private
107
113
  super.method_missing
108
114
  end
109
115
  end
110
-
116
+
111
117
  # @return [String] 'documents' or 'document' as indicated by expectation
112
118
  def doc_label_str(expectations)
113
- # FIXME: must be a better way to do pluralize and inflection fun
114
- if expectations.is_a?(Array) &&
115
- (expectations.size > 1 || (expectations.first.is_a?(Array) && expectations.first.size > 1))
116
- docs = "documents"
119
+ # FIXME: must be a better way to do pluralize and inflection fun
120
+ if expectations.is_a?(Array) &&
121
+ (expectations.size > 1 || (expectations.first.is_a?(Array) && expectations.first.size > 1))
122
+ 'documents'
123
+ else
124
+ 'document'
125
+ end
126
+ end
127
+
128
+ def to_sentence(words = [])
129
+ words = words.map(&:inspect)
130
+ case words.length
131
+ when 0
132
+ ''
133
+ when 1
134
+ " #{words[0]}"
135
+ when 2
136
+ " #{words[0]} and #{words[1]}"
117
137
  else
118
- docs = "document"
138
+ " #{words[0...-1].join(', ')}, and #{words[-1]}"
119
139
  end
120
140
  end
121
-
122
141
  end # class Include
123
142
  end # module BuiltIn
124
143
  end
125
- end
144
+ end
@@ -1,37 +1,35 @@
1
1
  require 'delegate'
2
2
 
3
3
  class RSpecSolr
4
-
5
4
  # Subclass Hash so we can use RSpec matchers for number of documents:
6
5
  # my_solr_resp_hash.should have(3).documents
7
6
  # my_solr_resp_hash.should have_at_least(3).documents
8
7
  # NOTE: to use has_document?(String) to match documents, set the id_field attribute (defaults to 'id')
9
8
  class SolrResponseHash < DelegateClass(Hash)
10
-
11
9
  # unique id field for Solr documents; defaults to 'id', can be changed with .id_field='foo'
12
10
  attr_accessor :id_field
13
-
14
- # id_field attribute defaults to 'id'
11
+
12
+ # id_field attribute defaults to 'id'
15
13
  def id_field
16
14
  @id_field ||= 'id'
17
15
  end
18
-
16
+
19
17
  # NOTE: this is about the TOTAL number of Solr documents matching query, not the number of docs in THIS response
20
18
  # override Hash size method so we can use RSpec matchers for number of documents:
21
19
  # my_solr_resp_hash.should have(3).documents
22
20
  # my_solr_resp_hash.should have_at_least(3).documents
23
21
  def size
24
- self["response"] ? self["response"]["numFound"] : 0 # total number of Solr docs matching query
22
+ self['response'] ? self['response']['numFound'] : 0 # total number of Solr docs matching query
25
23
  # NOT: self["response"]["docs"].size # number of Solr docs returned in THIS response
26
24
  end
27
-
25
+
28
26
  # @return true if THIS Solr Response contains document(s) as indicated by expected_doc
29
27
  # @param expected_doc what should be matched in a document in THIS response
30
28
  # @example expected_doc Hash implies ALL key/value pairs will be matched in a SINGLE Solr document
31
29
  # {"id" => "666"}
32
30
  # {"subject" => ["warm fuzzies", "fluffy"]}
33
31
  # {"title" => "warm fuzzies", "subject" => ["puppies"]}
34
- # @example expected_doc String
32
+ # @example expected_doc String
35
33
  # "666" implies {'id' => '666'} when id_field is 'id'
36
34
  # @example expected_doc Array
37
35
  # ["1", "2", "3"] implies we expect Solr docs with ids 1, 2, 3 included in this response
@@ -45,41 +43,41 @@ class RSpecSolr
45
43
  (first_non_match ? first_non_match >= max_doc_position : true)
46
44
  else
47
45
  # we are happy if any doc meets all of our expectations
48
- docs.any? { |doc|
46
+ docs.any? do |doc|
49
47
  doc_matches_all_criteria(doc, expected_doc) &&
50
48
  # satisfy doc's position in the results
51
49
  (max_doc_position ? docs.find_index(doc) < max_doc_position : true)
52
- }
50
+ end
53
51
  end
54
52
  elsif expected_doc.is_a?(String)
55
53
  if all_must_match
56
- raise ArgumentError, "in_each_of_first(n) requires a Hash argument to include() method"
54
+ fail ArgumentError, 'in_each_of_first(n) requires a Hash argument to include() method'
57
55
  end
58
- has_document?({self.id_field => expected_doc}, max_doc_position)
56
+ has_document?({ id_field => expected_doc }, max_doc_position)
59
57
  elsif expected_doc.is_a?(Array)
60
58
  if all_must_match
61
- raise ArgumentError, "in_each_of_first(n) requires a Hash argument to include() method"
59
+ fail ArgumentError, 'in_each_of_first(n) requires a Hash argument to include() method'
62
60
  end
63
61
  expected_doc.all? { |exp| has_document?(exp, max_doc_position) }
64
- end
62
+ end
65
63
  end
66
64
 
67
65
  # return true if the document contains all the key value pairs in the expectations_hash
68
66
  def doc_matches_all_criteria(doc, expectations_hash)
69
- expectations_hash.all? { | exp_fname, exp_vals |
70
- doc.include?(exp_fname) &&
67
+ expectations_hash.all? do |exp_fname, exp_vals|
68
+ doc.include?(exp_fname) &&
71
69
  # exp_vals can be a String or an Array
72
70
  # if it's an Array, then all expected values must be present
73
- Array(exp_vals).all? { | exp_val |
71
+ Array(exp_vals).all? do |exp_val|
74
72
  # a doc's fld values can be a String or an Array
75
73
  case exp_val
76
- when Regexp
77
- Array(doc[exp_fname]).any? { |val| val =~ exp_val }
78
- else
79
- Array(doc[exp_fname]).include?(exp_val)
74
+ when Regexp
75
+ Array(doc[exp_fname]).any? { |val| val =~ exp_val }
76
+ else
77
+ Array(doc[exp_fname]).include?(exp_val)
80
78
  end
81
- }
82
- }
79
+ end
80
+ end
83
81
  end
84
82
 
85
83
  # @return the index of the first document that meets the expectations in THIS response
@@ -88,60 +86,60 @@ class RSpecSolr
88
86
  # {"id" => "666"}
89
87
  # {"subject" => ["warm fuzzies", "fluffy"]}
90
88
  # {"title" => "warm fuzzies", "subject" => ["puppies"]}
91
- # @example expected_doc String
89
+ # @example expected_doc String
92
90
  # "666" implies {'id' => '666'} when id_field is 'id'
93
91
  # @example expected_doc Array
94
92
  # ["1", "2", "3"] implies we expect Solr docs with ids 1, 2, 3 included in this response
95
93
  # [{"title" => "warm fuzzies"}, {"title" => "cool fuzzies"}] implies we expect at least one Solr doc in this response matching each Hash in the Array
96
94
  def get_first_doc_index(expected_doc)
97
- # FIXME: DRY it up! -- very similar to has_document
95
+ # FIXME: DRY it up! -- very similar to has_document
98
96
  if expected_doc.is_a?(Hash)
99
97
  # we are happy if any doc meets all of our expectations
100
- docs.any? { |doc|
101
- expected_doc.all? { | exp_fname, exp_vals |
102
- if (doc.include?(exp_fname) &&
103
- # exp_vals can be a String or an Array
104
- # if it's an Array, then all expected values must be present
105
- Array(exp_vals).all? { | exp_val |
106
- # a doc's fld values can be a String or an Array
107
- Array(doc[exp_fname]).include?(exp_val)
108
- })
98
+ docs.any? do |doc|
99
+ expected_doc.all? do |exp_fname, exp_vals|
100
+ if doc.include?(exp_fname) &&
101
+ # exp_vals can be a String or an Array
102
+ # if it's an Array, then all expected values must be present
103
+ Array(exp_vals).all? do |exp_val|
104
+ # a doc's fld values can be a String or an Array
105
+ Array(doc[exp_fname]).include?(exp_val)
106
+ end
109
107
  first_doc_index = get_min_index(first_doc_index, docs.find_index(doc))
110
108
  return first_doc_index
111
109
  end
112
- }
113
- }
110
+ end
111
+ end
114
112
  elsif expected_doc.is_a?(String)
115
- first_doc_index = get_min_index(first_doc_index, get_first_doc_index({self.id_field => expected_doc}))
113
+ first_doc_index = get_min_index(first_doc_index, get_first_doc_index(id_field => expected_doc))
116
114
  elsif expected_doc.is_a?(Array)
117
- expected_doc.all? { |exp|
115
+ expected_doc.all? do |exp|
118
116
  ix = get_first_doc_index(exp)
119
117
  if ix
120
118
  first_doc_index = get_min_index(first_doc_index, ix)
121
119
  else
122
120
  return nil
123
121
  end
124
- }
125
- end
122
+ end
123
+ end
126
124
 
127
- return first_doc_index
125
+ first_doc_index
128
126
  end
129
127
 
130
128
  # @return String containing response header and numFound parts of hash for readable output for number of docs messages
131
129
  def num_docs_partial_output_str
132
- "{'responseHeader' => #{self['responseHeader'].inspect}, " +
133
- (self['response'] ? "'response' => {'numFound' => #{self['response']['numFound']}, ...}" : "" ) +
134
- " ... }"
130
+ "{'responseHeader' => #{self['responseHeader'].inspect}, " +
131
+ (self['response'] ? "'response' => {'numFound' => #{self['response']['numFound']}, ...}" : '') +
132
+ ' ... }'
135
133
  end
136
-
134
+
137
135
  # @return true if the Solr response contains the facet field indicated and the facet field has some values; return false otherwise
138
136
  def has_facet_field_with_value?(ff_name, facet_val = nil)
139
- if self["facet_counts"] && self["facet_counts"]["facet_fields"] && self["facet_counts"]["facet_fields"][ff_name]
137
+ if self['facet_counts'] && self['facet_counts']['facet_fields'] && self['facet_counts']['facet_fields'][ff_name]
140
138
  if facet_val
141
- val_count_array = self["facet_counts"]["facet_fields"][ff_name]
142
- return val_count_array.each_slice(2).find { |val_count| val_count[0] == facet_val}
139
+ val_count_array = self['facet_counts']['facet_fields'][ff_name]
140
+ return val_count_array.each_slice(2).find { |val_count| val_count[0] == facet_val }
143
141
  else
144
- self["facet_counts"]["facet_fields"][ff_name].size > 0
142
+ self['facet_counts']['facet_fields'][ff_name].size > 0
145
143
  end
146
144
  else
147
145
  false
@@ -150,15 +148,15 @@ class RSpecSolr
150
148
 
151
149
  # @return true if the Solr response contains the facet field indicated and the facet field has some values; return false otherwise
152
150
  def has_facet_field?(ff_name)
153
- if self["facet_counts"] && self["facet_counts"]["facet_fields"] && self["facet_counts"]["facet_fields"][ff_name]
154
- self["facet_counts"]["facet_fields"][ff_name]
151
+ if self['facet_counts'] && self['facet_counts']['facet_fields'] && self['facet_counts']['facet_fields'][ff_name]
152
+ self['facet_counts']['facet_fields'][ff_name]
155
153
  else
156
154
  false
157
155
  end
158
156
  end
159
157
 
160
- private
161
-
158
+ private
159
+
162
160
  # return the minimum of the two arguments. If one of the arguments is nil, then return the other argument.
163
161
  # If both arguments are nil, return nil.
164
162
  def get_min_index(a, b)
@@ -175,8 +173,7 @@ private
175
173
 
176
174
  # access the Array of Hashes representing the Solr documents in the response
177
175
  def docs
178
- @docs ||= self["response"]["docs"]
176
+ @docs ||= self['response']['docs']
179
177
  end
180
-
181
178
  end
182
179
  end