rspec-solr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rvmrc +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +5 -0
- data/README.rdoc +31 -0
- data/Rakefile +19 -0
- data/expectation_matchers.rdoc +148 -0
- data/lib/rspec-solr/have_documents_matcher.rb +34 -0
- data/lib/rspec-solr/include_documents_matcher.rb +105 -0
- data/lib/rspec-solr/solr_response_hash.rb +130 -0
- data/lib/rspec-solr/version.rb +3 -0
- data/lib/rspec-solr.rb +7 -0
- data/lib/tasks/ci.rake +5 -0
- data/lib/tasks/doc.rake +21 -0
- data/lib/tasks/spec.rake +5 -0
- data/rspec-solr.gemspec +35 -0
- data/spec/have_documents_spec.rb +60 -0
- data/spec/include_before_spec.rb +107 -0
- data/spec/include_document_spec.rb +423 -0
- data/spec/include_in_first_n_spec.rb +229 -0
- data/spec/number_of_documents_spec.rb +250 -0
- data/spec/solr_response_hash_spec.rb +115 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/matchers.rb +22 -0
- metadata +202 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.3@rspec-solr --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
Copyright (c) 2012. The Board of Trustees of the Leland Stanford Junior University. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use of this distribution in source and binary forms, with or without modification, are permitted provided that: The above copyright notice and this permission notice appear in all copies and supporting documentation; The name, identifiers, and trademarks of The Board of Trustees of the Leland Stanford Junior University are not used in advertising or publicity without the express prior written permission of The Board of Trustees of the Leland Stanford Junior University; Recipients acknowledge that this distribution is made available as a research courtesy, "as is", potentially with defects, without any obligation on the part of The Board of Trustees of the Leland Stanford Junior University to provide support, services, or repair;
|
4
|
+
|
5
|
+
THE BOARD OF TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE BOARD OF TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
= RSpec Solr
|
2
|
+
|
3
|
+
{<img src="https://secure.travis-ci.org/sul-dlss/rspec-solr.png?branch=master" alt="Build Status" />}[http://travis-ci.org/sul-dlss/rspec-solr]
|
4
|
+
|
5
|
+
Provides RSpec custom matchers to be used with Solr response objects.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'rspec-solr'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install rspec-solr
|
20
|
+
|
21
|
+
== Usage
|
22
|
+
|
23
|
+
See expecation_matchers.rdoc
|
24
|
+
|
25
|
+
== Contributing
|
26
|
+
|
27
|
+
1. Fork it
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/setup'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
require 'rake'
|
6
|
+
require 'bundler'
|
7
|
+
|
8
|
+
begin
|
9
|
+
Bundler.setup(:default, :development)
|
10
|
+
rescue Bundler::BundlerError => e
|
11
|
+
$stderr.puts e.message
|
12
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
13
|
+
exit e.status_code
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir.glob('lib/tasks/*.rake').each { |r| import r }
|
17
|
+
|
18
|
+
task :default => :ci
|
19
|
+
task :spec => :rspec
|
@@ -0,0 +1,148 @@
|
|
1
|
+
= RSpec-Solr Matchers for RSpec-Solr::SolrResponseHash
|
2
|
+
|
3
|
+
(rspec-solr_resp_hash..should ... or rspec-solr_resp_hash..should_not ...)
|
4
|
+
|
5
|
+
== MATCHING WHETHER RESPONSE HAS DOCUMENTS OR NOT
|
6
|
+
|
7
|
+
NOTE: this is about the TOTAL number of Solr documents that match ("numFound"), NOT solely the documents returned in this response
|
8
|
+
|
9
|
+
=== Matcher
|
10
|
+
* have_documents
|
11
|
+
|
12
|
+
=== Usage
|
13
|
+
* rspec-solr_resp_hash.should have_documents
|
14
|
+
* rspec-solr_resp_hash.should_not have_documents
|
15
|
+
|
16
|
+
== MATCHING NUMBER OF DOCUMENTS
|
17
|
+
|
18
|
+
NOTE: this is about the TOTAL number of Solr documents that match ("numFound"), NOT solely the documents returned in this response
|
19
|
+
|
20
|
+
=== Matchers
|
21
|
+
* have(2).documents
|
22
|
+
* have_exactly(4).documents
|
23
|
+
* have_at_least(3).documents
|
24
|
+
* have_at_most(3).documents
|
25
|
+
|
26
|
+
=== Usage
|
27
|
+
* rspec-solr_resp_hash.should have(3).documents
|
28
|
+
* rspec-solr_resp_hash.should_not have(2).documents
|
29
|
+
* rspec-solr_resp_hash.should have_at_least(3).documents
|
30
|
+
* rspec-solr_resp_hash.should have_at_most(4).documents
|
31
|
+
|
32
|
+
== MATCHING SPECIFIC DOCUMENTS IN RESPONSE
|
33
|
+
|
34
|
+
NOTE: this is about the Solr documents returned in THIS response
|
35
|
+
|
36
|
+
=== Matcher
|
37
|
+
* include()
|
38
|
+
|
39
|
+
=== Usage
|
40
|
+
==== Specifying Single Document
|
41
|
+
===== String
|
42
|
+
* ("idval")
|
43
|
+
NOTE: value of the unique id field (defaults to 'id') in the Solr document
|
44
|
+
To change the id field name, use my_solr_response_hash.id_field='my_id_fldname'
|
45
|
+
===== Hash
|
46
|
+
* ("fldname" => "value")
|
47
|
+
* ("fld1" => "val1", "fld2" => "val2")
|
48
|
+
NOTE: single Solr document must have all key value pairs
|
49
|
+
* ("fldname" => ["val1", "val2", "val3"])
|
50
|
+
NOTE: all of the Array values must be present for the fld in a single Solr document
|
51
|
+
should_not for Array implies NONE of the values should be present in a single document
|
52
|
+
TODO:
|
53
|
+
* document("fldname" => "/regex/")
|
54
|
+
* include_title("val") include_[any_field_name]("val")
|
55
|
+
|
56
|
+
==== Specifying Multiple Documents
|
57
|
+
===== Array
|
58
|
+
* by id strings: (["id1", "id2", "id3"])
|
59
|
+
* by hashes: ([{"title" => "green is best"}, {"title" => "blue is best"}, {"fld" => "val"}])
|
60
|
+
NOTE: you cannot do this: ([{"title" => ["Solr doc 1 title", "Solr doc 2 title"]} ]) to specify multiple documents
|
61
|
+
* by mix of id strings and hashes: ([{"title" => "green is best"}, "id3", {"author" => "steinbeck"}])
|
62
|
+
|
63
|
+
==== Full Examples
|
64
|
+
* rspec-solr_resp_hash.should include("fld1" => "val1")
|
65
|
+
* rspec-solr_resp_hash.should include("f1" => "v1", "f2" => ["val1", "val2", "val3"])
|
66
|
+
* rspec-solr_resp_hash.should include("idval")
|
67
|
+
* rspec-solr_resp_hash.should include(["id1", "id2", "id3"])
|
68
|
+
* rspec-solr_resp_hash.should include([{"title" => "title1"}, {"title" => "title2"}])
|
69
|
+
* rspec-solr_resp_hash.should include([{"title" => "title1"}, {"title" => "title2"}, "id8"])
|
70
|
+
|
71
|
+
== MATCHING SPECIFIC DOCUMENTS OCCURRING IN FIRST N RESULTS
|
72
|
+
|
73
|
+
NOTE: this is about the Solr documents returned in THIS response
|
74
|
+
|
75
|
+
=== Matchers
|
76
|
+
* include().as_first
|
77
|
+
* include().as_first.document
|
78
|
+
* include().as_first.results
|
79
|
+
* include().as_first.anything.can.go.here
|
80
|
+
* include().in_first(n)
|
81
|
+
* include().in_first(n).documents
|
82
|
+
* include().in_first(n).results
|
83
|
+
* include().in_first(n).anything_can_go_here
|
84
|
+
* include(Array).in_first(n).results
|
85
|
+
|
86
|
+
TODO:
|
87
|
+
* include_at_least(3).of_these_documents().in_first(3).results
|
88
|
+
|
89
|
+
=== Usage
|
90
|
+
See above for information on how to specify specific documents
|
91
|
+
* rspec-solr_resp_hash.should include("111").as_first.document
|
92
|
+
* rspec-solr_resp_hash.should include(["111", "222"]).as_first.documents
|
93
|
+
* rspec-solr_resp_hash.should include([{"title" => "title1"}, {"title" => "title2"}]).in_first(3).results
|
94
|
+
* rspec-solr_resp_hash.should include("fld1" => "val1").in_first(3)
|
95
|
+
|
96
|
+
|
97
|
+
== MATCHING RELATIVE ORDER OF SPECIFIC DOCUMENTS
|
98
|
+
|
99
|
+
NOTE: this is about the Solr documents returned in THIS response
|
100
|
+
|
101
|
+
=== Matcher
|
102
|
+
* include().before()
|
103
|
+
NOTE: documents are specified the same way inside both sets of parens (see Usage for examples and see above re: specifying documents)
|
104
|
+
|
105
|
+
TODO: Potential Syntax:
|
106
|
+
* include().before_first_occurrence_of()
|
107
|
+
* include().within(3).of_document()
|
108
|
+
* subject.document(:title => 'vala').should come_before(subject.document(:title =>'valb'))
|
109
|
+
* subject.should have_result_field_ordered("title", "vala", "valb")
|
110
|
+
|
111
|
+
=== Usage
|
112
|
+
* rspec-solr_resp_hash.should include("111").before("222")
|
113
|
+
* rspec-solr_resp_hash.should include("fld"=>"val").before("fld"=>["val1", "val2", "val3"])
|
114
|
+
* rspec-solr_resp_hash.should include([{"title" => "title1"}, {"title" => "title2"}]).before("title" => "title3")
|
115
|
+
|
116
|
+
== TODO: MATCHING FACET VALUES IN RESPONSE
|
117
|
+
|
118
|
+
NOTE: this is about the facet values returned in THIS response
|
119
|
+
|
120
|
+
=== Specifying Facet Values
|
121
|
+
* facet(:format => "Book")
|
122
|
+
* facets(:format => ["Image", "Map"])
|
123
|
+
* facets with arrarr and without
|
124
|
+
|
125
|
+
=== Potential Syntax
|
126
|
+
rspec-solr_resp_hash.should include_facet
|
127
|
+
|
128
|
+
=== Matchers
|
129
|
+
* include_facet().before_facet()
|
130
|
+
* include_facets().before_facet()
|
131
|
+
* include_facet().before_facets()
|
132
|
+
* include_facets().before_facets()
|
133
|
+
|
134
|
+
|
135
|
+
== TODO: COMPARING TOTAL RESULTS OF TWO RESPONSES
|
136
|
+
|
137
|
+
=== Specifying Second Response
|
138
|
+
* than_solr_response({:q => "foo", :sort => "title"}) # solr params
|
139
|
+
* than_search_for("foo") # only q is specified, defaults for everything else
|
140
|
+
|
141
|
+
=== Potential Syntax
|
142
|
+
rspec-solr_resp_hash.should
|
143
|
+
|
144
|
+
=== Matchers
|
145
|
+
* have_more_results_than_solr_response(:params)
|
146
|
+
* have_fewer_results_than_solr_response(:params)
|
147
|
+
* have_the_same_number_of_results_as_solr_response(:params)
|
148
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'rspec-expectations'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
6
|
+
|
7
|
+
# Custom RSpec Matchers for Solr responses
|
8
|
+
module RSpecSolr::Matchers
|
9
|
+
|
10
|
+
# Determine if the receiver has Solr documents
|
11
|
+
# NOTE: this is about the TOTAL number of Solr documents matching the query, not solely the number of docs in THIS response
|
12
|
+
def have_documents
|
13
|
+
# Placeholder method for documentation purposes;
|
14
|
+
# the actual method is defined using RSpec's matcher DSL
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define .have_documents
|
18
|
+
# Determine if the receiver (a Solr response object) has at least one document
|
19
|
+
# NOTE: this is about the TOTAL number of Solr documents matching the query, not solely the number of docs in THIS response
|
20
|
+
RSpec::Matchers.define :have_documents do
|
21
|
+
match do |solr_resp|
|
22
|
+
solr_resp["response"]["numFound"] > 0
|
23
|
+
end
|
24
|
+
|
25
|
+
failure_message_for_should do |solr_resp|
|
26
|
+
"expected documents in Solr response #{solr_resp["response"]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
failure_message_for_should_not do |solr_resp|
|
30
|
+
"did not expect documents, but Solr response had #{solr_resp["response"]["numFound"]}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# overriding RSpec::Matchers::Builtin::Include.perform_match method
|
2
|
+
# so we can use RSpec include matcher for document in Solr response
|
3
|
+
module RSpec
|
4
|
+
module Matchers
|
5
|
+
module BuiltIn
|
6
|
+
class Include
|
7
|
+
|
8
|
+
# chain method for .should include().in_first(n)
|
9
|
+
# sets @max_doc_position for use in perform_match method
|
10
|
+
# @return self - "each method must return self in order to chain methods together"
|
11
|
+
def in_first(num=1)
|
12
|
+
@max_doc_position = num
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :as_first, :in_first
|
17
|
+
|
18
|
+
# chain method for .should include().before()
|
19
|
+
# sets @before_expected for use in perform_match method
|
20
|
+
# @return self - "each method must return self in order to chain methods together"
|
21
|
+
def before(expected)
|
22
|
+
# get first doc position by calling has_document ???
|
23
|
+
@before_expected = expected
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# override failure message for improved readability
|
28
|
+
def failure_message_for_should
|
29
|
+
assert_ivars :@actual, :@expected
|
30
|
+
# FIXME: DRY up these messages across cases and across should and should_not
|
31
|
+
if @before_expected
|
32
|
+
"expected #{@actual.inspect} to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} before #{doc_label_str(@before_expected)} matching #{@before_expected.inspect}"
|
33
|
+
elsif @max_doc_position
|
34
|
+
"expected #{@actual.inspect} to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} in first #{@max_doc_position.to_s} results"
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# override failure message for improved readability
|
41
|
+
def failure_message_for_should_not
|
42
|
+
assert_ivars :@actual, :@expected
|
43
|
+
if @before_expected
|
44
|
+
"expected #{@actual.inspect} not to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} before #{doc_label_str(@before_expected)} matching #{@before_expected.inspect}"
|
45
|
+
elsif @max_doc_position
|
46
|
+
"expected #{@actual.inspect} not to #{name_to_sentence} #{doc_label_str(@expected)}#{expected_to_sentence} in first #{@max_doc_position.to_s} results"
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
private
|
54
|
+
# overriding method so we can use RSpec include matcher for document in Solr response
|
55
|
+
# my_solr_resp_hash.should include({"id" => "666"})
|
56
|
+
def perform_match(predicate, hash_predicate, actuals, expecteds)
|
57
|
+
expecteds.send(predicate) do |expected|
|
58
|
+
if comparing_doc_to_solr_resp_hash?(actuals, expected)
|
59
|
+
if (@before_expected)
|
60
|
+
before_ix = actuals.get_first_doc_index(@before_expected)
|
61
|
+
if before_ix
|
62
|
+
@max_doc_position = before_ix + 1
|
63
|
+
else
|
64
|
+
# make the desired result impossible
|
65
|
+
@max_doc_position = -1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
actuals.has_document?(expected, @max_doc_position)
|
69
|
+
elsif comparing_hash_values?(actuals, expected)
|
70
|
+
expected.send(hash_predicate) {|k,v| actuals[k] == v}
|
71
|
+
elsif comparing_hash_keys?(actuals, expected)
|
72
|
+
actuals.has_key?(expected)
|
73
|
+
else
|
74
|
+
actuals.include?(expected)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# is actual param a SolrResponseHash?
|
80
|
+
def comparing_doc_to_solr_resp_hash?(actual, expected)
|
81
|
+
actual.is_a?(RSpecSolr::SolrResponseHash)
|
82
|
+
end
|
83
|
+
|
84
|
+
def method_missing(method, *args, &block)
|
85
|
+
@collection_name = method
|
86
|
+
@args = args
|
87
|
+
@block = block
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] 'documents' or 'document' as indicated by expectation
|
92
|
+
def doc_label_str(expectations)
|
93
|
+
# FIXME: must be a better way to do pluralize and inflection fun
|
94
|
+
if expectations.is_a?(Array) &&
|
95
|
+
(expectations.size > 1 || (expectations.first.is_a?(Array) && expectations.first.size > 1))
|
96
|
+
docs = "documents"
|
97
|
+
else
|
98
|
+
docs = "document"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end # class Include
|
103
|
+
end # module BuiltIn
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
class RSpecSolr
|
4
|
+
|
5
|
+
# Subclass Hash so we can use RSpec matchers for number of documents:
|
6
|
+
# my_solr_resp_hash.should have(3).documents
|
7
|
+
# my_solr_resp_hash.should have_at_least(3).documents
|
8
|
+
# NOTE: to use has_document?(String) to match documents, set the id_field attribute (defaults to 'id')
|
9
|
+
class SolrResponseHash < DelegateClass(Hash)
|
10
|
+
|
11
|
+
# unique id field for Solr documents; defaults to 'id', can be changed with .id_field='foo'
|
12
|
+
attr_accessor :id_field
|
13
|
+
|
14
|
+
# id_field attribute defaults to 'id'
|
15
|
+
def id_field
|
16
|
+
@id_field ||= 'id'
|
17
|
+
end
|
18
|
+
|
19
|
+
# NOTE: this is about the TOTAL number of Solr documents matching query, not the number of docs in THIS response
|
20
|
+
# override Hash size method so we can use RSpec matchers for number of documents:
|
21
|
+
# my_solr_resp_hash.should have(3).documents
|
22
|
+
# my_solr_resp_hash.should have_at_least(3).documents
|
23
|
+
def size
|
24
|
+
self["response"]["numFound"] # total number of Solr docs matching query
|
25
|
+
# NOT: self["response"]["docs"].size # number of Solr docs returned in THIS response
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return true if THIS Solr Response contains document(s) as indicated by expected_doc
|
29
|
+
# @param expected_doc what should be matched in a document in THIS response
|
30
|
+
# @example expected_doc Hash implies ALL key/value pairs will be matched in a SINGLE Solr document
|
31
|
+
# {"id" => "666"}
|
32
|
+
# {"subject" => ["warm fuzzies", "fluffy"]}
|
33
|
+
# {"title" => "warm fuzzies", "subject" => ["puppies"]}
|
34
|
+
# @example expected_doc String
|
35
|
+
# "666" implies {'id' => '666'} when id_field is 'id'
|
36
|
+
# @example expected_doc Array
|
37
|
+
# ["1", "2", "3"] implies we expect Solr docs with ids 1, 2, 3 included in this response
|
38
|
+
# [{"title" => "warm fuzzies"}, {"title" => "cool fuzzies"}] implies we expect at least one Solr doc in this response matching each Hash in the Array
|
39
|
+
# @param [FixNum] max_doc_position maximum acceptable position (1-based) of document in results. (e.g. if 2, it must be the 1st or 2nd doc in the results)
|
40
|
+
def has_document?(expected_doc, max_doc_position = nil)
|
41
|
+
if expected_doc.is_a?(Hash)
|
42
|
+
# we are happy if any doc meets all of our expectations
|
43
|
+
docs.any? { |doc|
|
44
|
+
expected_doc.all? { | exp_fname, exp_vals |
|
45
|
+
doc.include?(exp_fname) &&
|
46
|
+
# exp_vals can be a String or an Array
|
47
|
+
# if it's an Array, then all expected values must be present
|
48
|
+
Array(exp_vals).all? { | exp_val |
|
49
|
+
# a doc's fld values can be a String or an Array
|
50
|
+
Array(doc[exp_fname]).include?(exp_val)
|
51
|
+
} &&
|
52
|
+
# satisfy doc's position in the results
|
53
|
+
(max_doc_position ? docs.find_index(doc) < max_doc_position : true)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
elsif expected_doc.is_a?(String)
|
57
|
+
has_document?({self.id_field => expected_doc}, max_doc_position)
|
58
|
+
elsif expected_doc.is_a?(Array)
|
59
|
+
expected_doc.all? { |exp| has_document?(exp, max_doc_position) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return the index of the first document that meets the expectations in THIS response
|
64
|
+
# @param expected_doc what should be matched in a document in THIS response
|
65
|
+
# @example expected_doc Hash implies ALL key/value pairs will be matched in a SINGLE Solr document
|
66
|
+
# {"id" => "666"}
|
67
|
+
# {"subject" => ["warm fuzzies", "fluffy"]}
|
68
|
+
# {"title" => "warm fuzzies", "subject" => ["puppies"]}
|
69
|
+
# @example expected_doc String
|
70
|
+
# "666" implies {'id' => '666'} when id_field is 'id'
|
71
|
+
# @example expected_doc Array
|
72
|
+
# ["1", "2", "3"] implies we expect Solr docs with ids 1, 2, 3 included in this response
|
73
|
+
# [{"title" => "warm fuzzies"}, {"title" => "cool fuzzies"}] implies we expect at least one Solr doc in this response matching each Hash in the Array
|
74
|
+
def get_first_doc_index(expected_doc)
|
75
|
+
# FIXME: DRY it up! -- very similar to has_document
|
76
|
+
if expected_doc.is_a?(Hash)
|
77
|
+
# we are happy if any doc meets all of our expectations
|
78
|
+
docs.any? { |doc|
|
79
|
+
expected_doc.all? { | exp_fname, exp_vals |
|
80
|
+
if (doc.include?(exp_fname) &&
|
81
|
+
# exp_vals can be a String or an Array
|
82
|
+
# if it's an Array, then all expected values must be present
|
83
|
+
Array(exp_vals).all? { | exp_val |
|
84
|
+
# a doc's fld values can be a String or an Array
|
85
|
+
Array(doc[exp_fname]).include?(exp_val)
|
86
|
+
})
|
87
|
+
first_doc_index = get_min_index(first_doc_index, docs.find_index(doc))
|
88
|
+
return first_doc_index
|
89
|
+
end
|
90
|
+
}
|
91
|
+
}
|
92
|
+
elsif expected_doc.is_a?(String)
|
93
|
+
first_doc_index = get_min_index(first_doc_index, get_first_doc_index({self.id_field => expected_doc}))
|
94
|
+
elsif expected_doc.is_a?(Array)
|
95
|
+
expected_doc.all? { |exp|
|
96
|
+
ix = get_first_doc_index(exp)
|
97
|
+
if ix
|
98
|
+
first_doc_index = get_min_index(first_doc_index, ix)
|
99
|
+
else
|
100
|
+
return nil
|
101
|
+
end
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
return first_doc_index
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# return the minimum of the two arguments. If one of the arguments is nil, then return the other argument.
|
111
|
+
# If both arguments are nil, return nil.
|
112
|
+
def get_min_index(a, b)
|
113
|
+
if a
|
114
|
+
if b
|
115
|
+
[a, b].min
|
116
|
+
else # b is nil
|
117
|
+
a
|
118
|
+
end
|
119
|
+
else # a is nil
|
120
|
+
b
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# access the Array of Hashes representing the Solr documents in the response
|
125
|
+
def docs
|
126
|
+
@docs ||= self["response"]["docs"]
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
data/lib/rspec-solr.rb
ADDED
data/lib/tasks/ci.rake
ADDED
data/lib/tasks/doc.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'yard'
|
2
|
+
require 'yard/rake/yardoc_task'
|
3
|
+
|
4
|
+
# Use yard to build docs
|
5
|
+
begin
|
6
|
+
project_root = File.expand_path(File.dirname(__FILE__) + "/../..")
|
7
|
+
doc_dest_dir = File.join(project_root, 'doc')
|
8
|
+
|
9
|
+
YARD::Rake::YardocTask.new(:doc) do |yt|
|
10
|
+
yt.files = Dir.glob(File.join(project_root, 'lib', '**', '*.rb'))
|
11
|
+
yt.options = ['--output-dir', doc_dest_dir,
|
12
|
+
'--readme', 'README.rdoc',
|
13
|
+
'--title', 'RSpec-Solr Documentation',
|
14
|
+
'--files', 'expectation_matchers.rdoc']
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
desc "Generate YARD Documentation"
|
18
|
+
task :doc do
|
19
|
+
abort "Please install the YARD gem to generate rdoc."
|
20
|
+
end
|
21
|
+
end
|
data/lib/tasks/spec.rake
ADDED
data/rspec-solr.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
# # -*- encoding: utf-8 -*-
|
3
|
+
require File.expand_path('../lib/rspec-solr/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "rspec-solr"
|
7
|
+
gem.version = RSpecSolr::VERSION
|
8
|
+
gem.authors = ["Naomi Dushay", "Chris Beer"]
|
9
|
+
gem.email = ["ndushay@stanford.edu", "cabeer@stanford.edu"]
|
10
|
+
gem.description = "Provides RSpec custom matchers to be used with Solr response objects."
|
11
|
+
gem.summary = "RSpec custom matchers for Solr response objects"
|
12
|
+
gem.homepage = "http://github.com/sul-dlss/rspec-solr"
|
13
|
+
|
14
|
+
gem.extra_rdoc_files = ["LICENSE.txt", "README.rdoc", "expectation_matchers.rdoc"]
|
15
|
+
gem.files = `git ls-files`.split($\)
|
16
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.require_path = ["lib"]
|
19
|
+
|
20
|
+
gem.add_runtime_dependency "rspec"
|
21
|
+
|
22
|
+
# Development dependencies
|
23
|
+
# Bundler will install these gems too if you've checked out rspec-solr source from git and run 'bundle install'
|
24
|
+
# It will not add these as dependencies if you require rspec-solr for other projects
|
25
|
+
gem.add_development_dependency "rake"
|
26
|
+
# docs
|
27
|
+
gem.add_development_dependency "rdoc"
|
28
|
+
gem.add_development_dependency "yard"
|
29
|
+
# tests for this gem
|
30
|
+
gem.add_development_dependency 'simplecov'
|
31
|
+
gem.add_development_dependency 'simplecov-rcov'
|
32
|
+
# continuous integration
|
33
|
+
gem.add_development_dependency 'travis-lint'
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rspec-solr'
|
3
|
+
|
4
|
+
describe RSpecSolr do
|
5
|
+
|
6
|
+
# fixtures below
|
7
|
+
|
8
|
+
context "have_documents with no doc matcher" do
|
9
|
+
it "passes if response.should has documents" do
|
10
|
+
@solr_resp_w_docs.should have_documents
|
11
|
+
@solr_resp_total_but_no_docs.should have_documents
|
12
|
+
end
|
13
|
+
|
14
|
+
it "passes if response.should_not has no documents" do
|
15
|
+
@solr_resp_no_docs.should_not have_documents
|
16
|
+
end
|
17
|
+
|
18
|
+
it "failure message for should" do
|
19
|
+
lambda {
|
20
|
+
@solr_resp_no_docs.should have_documents
|
21
|
+
}.should fail_matching("expected documents in Solr response ")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "failure message for should_not" do
|
25
|
+
lambda {
|
26
|
+
@solr_resp_w_docs.should_not have_documents
|
27
|
+
@solr_resp_total_but_no_docs.should_not have_documents
|
28
|
+
}.should fail_matching("did not expect documents, but Solr response had ")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
before(:all) do
|
33
|
+
@solr_resp_w_docs = { "response" =>
|
34
|
+
{ "numFound" => 5,
|
35
|
+
"start" => 0,
|
36
|
+
"docs" =>
|
37
|
+
[ {"id"=>"111"},
|
38
|
+
{"id"=>"222"},
|
39
|
+
{"id"=>"333"},
|
40
|
+
{"id"=>"444"},
|
41
|
+
{"id"=>"555"}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
@solr_resp_no_docs = { "response" =>
|
47
|
+
{ "numFound" => 0,
|
48
|
+
"start" => 0,
|
49
|
+
"docs" => []
|
50
|
+
}
|
51
|
+
}
|
52
|
+
@solr_resp_total_but_no_docs = { "response" =>
|
53
|
+
{ "numFound" => 1324,
|
54
|
+
"start" => 0,
|
55
|
+
"docs" => []
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|