rspec-solr 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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