bento_search 0.6.0 → 0.7.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 (63) hide show
  1. data/README.md +131 -74
  2. data/app/assets/javascripts/bento_search/ajax_load.js +12 -4
  3. data/app/assets/stylesheets/bento_search/suggested_styles.css +4 -4
  4. data/app/helpers/bento_search_helper.rb +114 -27
  5. data/app/item_decorators/bento_search/decorator_base.rb +53 -0
  6. data/app/item_decorators/bento_search/ebscohost/conditional_openurl_main_link.rb +36 -0
  7. data/app/item_decorators/bento_search/no_links.rb +3 -2
  8. data/app/item_decorators/bento_search/only_premade_openurl.rb +4 -0
  9. data/app/item_decorators/bento_search/openurl_add_other_link.rb +4 -0
  10. data/app/item_decorators/bento_search/openurl_main_link.rb +4 -0
  11. data/app/item_decorators/bento_search/standard_decorator.rb +122 -0
  12. data/app/models/bento_search/multi_searcher.rb +13 -6
  13. data/app/models/bento_search/openurl_creator.rb +25 -5
  14. data/app/models/bento_search/result_item.rb +25 -83
  15. data/app/models/bento_search/results/pagination.rb +8 -2
  16. data/app/models/bento_search/search_engine.rb +29 -23
  17. data/app/search_engines/bento_search/ebsco_host_engine.rb +161 -25
  18. data/app/search_engines/bento_search/eds_engine.rb +1 -44
  19. data/app/search_engines/bento_search/google_books_engine.rb +61 -14
  20. data/app/search_engines/bento_search/google_site_search_engine.rb +3 -1
  21. data/app/search_engines/bento_search/mock_engine.rb +4 -0
  22. data/app/search_engines/bento_search/primo_engine.rb +2 -3
  23. data/app/search_engines/bento_search/scopus_engine.rb +1 -0
  24. data/app/search_engines/bento_search/summon_engine.rb +5 -1
  25. data/app/search_engines/bento_search/worldcat_sru_dc_engine.rb +36 -8
  26. data/app/views/bento_search/_item_title.html.erb +29 -0
  27. data/app/views/bento_search/_no_results.html.erb +3 -0
  28. data/app/views/bento_search/_search_error.html.erb +19 -15
  29. data/app/views/bento_search/_std_item.html.erb +55 -30
  30. data/app/views/bento_search/search/search.html.erb +7 -0
  31. data/config/locales/en.yml +22 -0
  32. data/lib/bento_search/util.rb +63 -1
  33. data/lib/bento_search/version.rb +1 -1
  34. data/test/decorator/decorator_base_test.rb +72 -0
  35. data/test/decorator/standard_decorator_test.rb +55 -0
  36. data/test/dummy/db/development.sqlite3 +0 -0
  37. data/test/dummy/log/development.log +12 -0
  38. data/test/dummy/log/test.log +119757 -0
  39. data/test/functional/bento_search/search_controller_test.rb +28 -0
  40. data/test/helper/bento_search_helper_test.rb +71 -0
  41. data/test/helper/bento_truncate_helper_test.rb +71 -0
  42. data/test/unit/ebsco_host_engine_test.rb +110 -3
  43. data/test/unit/google_books_engine_test.rb +22 -14
  44. data/test/unit/google_site_search_test.rb +11 -4
  45. data/test/unit/item_decorators_test.rb +6 -65
  46. data/test/unit/openurl_creator_test.rb +87 -8
  47. data/test/unit/result_item_test.rb +1 -11
  48. data/test/unit/search_engine_base_test.rb +25 -2
  49. data/test/unit/search_engine_test.rb +16 -0
  50. data/test/unit/summon_engine_test.rb +3 -0
  51. data/test/vcr_cassettes/ebscohost/another_dissertation.yml +148 -0
  52. data/test/vcr_cassettes/ebscohost/dissertation_example.yml +218 -0
  53. data/test/vcr_cassettes/ebscohost/fulltext_info.yml +1306 -0
  54. data/test/vcr_cassettes/ebscohost/live_book_example.yml +130 -0
  55. data/test/vcr_cassettes/ebscohost/live_dissertation.yml +148 -0
  56. data/test/vcr_cassettes/ebscohost/live_pathological_book_item_example.yml +215 -0
  57. data/test/vcr_cassettes/google_site/gets_format_string.yml +232 -0
  58. data/test/vcr_cassettes/max_out_pagination.yml +155 -0
  59. data/test/vcr_cassettes/worldcat_sru_dc/max_out_pagination.yml +167 -0
  60. data/test/view/std_item_test.rb +25 -8
  61. metadata +45 -12
  62. data/test/unit/result_item_display_test.rb +0 -39
  63. data/test/unit/worldcat_sru_dc_engine_test.rb +0 -120
@@ -8,6 +8,12 @@ module BentoSearch
8
8
  config.allow_routable_results = true
9
9
  end
10
10
 
11
+ BentoSearch.register_engine("failed_response") do |config|
12
+ config.engine = "MockEngine"
13
+ config.allow_routable_results = true
14
+ config.error = {:message => "faked error"}
15
+ end
16
+
11
17
  BentoSearch.register_engine("not_routable") do |config|
12
18
  config.engine = "MockEngine"
13
19
  # no allow_routable_results
@@ -37,8 +43,30 @@ module BentoSearch
37
43
  assert_not_nil assigns(:results)
38
44
 
39
45
  assert_template "bento_search/search"
46
+
47
+ # meta tag with count
48
+ assert_tag(:tag => "meta", :attributes => {"itemprop" => "total_items", "content" => /^\d+$/ })
49
+ end
50
+
51
+ test "failed search" do
52
+ get :search, {:engine_id => "failed_response", :query => "my search"}
53
+
54
+ # should this really be a success? Yes, I think so, we don't
55
+ # want to stop ajax from getting it, it'll just have an error
56
+ # message in the HTML. Should it maybe have an html5 meta microdata
57
+ # warning?
58
+ assert_response :success
59
+
60
+ assert_template "bento_search/search"
61
+ assert_template "bento_search/_search_error"
62
+
63
+ assert_no_tag(:tag => "meta", :attributes => {"itemprop" => "total_items"})
40
64
  end
41
65
 
66
+
67
+
68
+
69
+
42
70
  test "custom layout config" do
43
71
  get :search, {:engine_id => "with_layout_config", :query => "my search"}
44
72
 
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  # this seems to work? Rails view testing is a mess.
@@ -163,6 +165,75 @@ class BentoSearchHelperTest < ActionView::TestCase
163
165
 
164
166
  assert_equal "No Key Test", key
165
167
  end
168
+
169
+ def test_field_hash_for
170
+ # generic
171
+ hash = bento_field_hash_for(nil)
172
+
173
+ assert_equal I18n.t("bento_search.search_fields").invert, hash
174
+
175
+ # specific engine
176
+ engine = MockEngine.new(:search_field_definitions => {
177
+ :mytitle => {:semantic => :title},
178
+ :myauthor => {:semantic => :author},
179
+ :myissn => {:semantic => :issn},
180
+ :mycustom => {}
181
+ })
182
+ hash = bento_field_hash_for(engine)
183
+ expected = { I18n.t("bento_search.search_fields.title") => 'title',
184
+ I18n.t("bento_search.search_fields.author") => 'author',
185
+ I18n.t("bento_search.search_fields.issn") => 'issn',
186
+ }
187
+ assert_equal expected, hash
188
+
189
+ # only
190
+ hash = bento_field_hash_for(engine, :only => :author)
191
+ assert_equal( {"Author" => "author"}, hash )
192
+ hash = bento_field_hash_for(engine, :only => ["author", "title"])
193
+ assert_equal( {"Title" => "title", "Author" => "author"}, hash )
194
+
195
+ # except
196
+
197
+
198
+ end
199
+
200
+ def test_bento_decorate
201
+ item = BentoSearch::ResultItem.new(:title => "foo")
202
+
203
+ decorated = bento_decorate(item)
204
+
205
+ assert_kind_of BentoSearch::StandardDecorator, decorated
206
+
207
+ assert_equal "foo", decorated.title
208
+
209
+ assert decorated.send("_h").respond_to?(:url_for), "has ActionView helpers available internally"
210
+ end
211
+
212
+ def test_bento_decorate_with_yield
213
+ item = BentoSearch::ResultItem.new(:title => "foo")
214
+
215
+ got_here = false
216
+
217
+ bento_decorate(item) do |decorated|
218
+ got_here = true
219
+ assert_equal "foo", decorated.title
220
+ end
221
+
222
+ assert got_here, "Yielded block is called"
223
+
224
+ end
225
+
226
+ class SomeDecorator < BentoSearch::StandardDecorator ; end
227
+
228
+ def test_bento_decorate_with_custom_decorator
229
+ item = BentoSearch::ResultItem.new(:title => "foo", :decorator => "BentoSearchHelperTest::SomeDecorator")
230
+
231
+ decorated = bento_decorate(item)
232
+
233
+ assert_kind_of BentoSearchHelperTest::SomeDecorator, decorated
234
+ end
235
+
236
+
166
237
 
167
238
 
168
239
  end
@@ -0,0 +1,71 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ # this seems to work? Rails view testing is a mess.
6
+ require 'sprockets/helpers/rails_helper'
7
+
8
+ class BentoSearchHelperTest < ActionView::TestCase
9
+ include BentoSearchHelper
10
+
11
+
12
+ def test_truncate_basic
13
+ # Basic test
14
+ output = bento_truncate("12345678901234567890", :length => 10)
15
+ assert_equal "123456789…", output
16
+ end
17
+
18
+ def test_truncate_tags
19
+ # With tags
20
+ html_input = "123456<p><b>78901234567</b>890</p>".html_safe
21
+ html_output = bento_truncate(html_input, :length => 10)
22
+ assert html_output.html_safe?, "truncated html_safe? is still html_safe?"
23
+ assert_equal "123456<p><b>789…</b></p>", html_output
24
+ end
25
+
26
+ def test_truncate_tag_boundary
27
+ # With break on tag boundary. Yes, there's an error not accounting
28
+ # for length of omission marker in this particular edge case,
29
+ # hard to fix, good enough for now.
30
+ html_input = "<p>1234567890<b>123456</b>7890</p>".html_safe
31
+ html_output = bento_truncate(html_input, :length => 10)
32
+ assert_equal "<p>1234567890…</p>", html_output
33
+ end
34
+
35
+ def test_truncate_boundary_edge_case
36
+ html_input = "12345<p>6789<b>0123456</b>7890</p>".html_safe
37
+ html_output = bento_truncate(html_input, :length => 10)
38
+ # yeah, weird elipses in <b> of their own, so it goes.
39
+ assert_equal "12345<p>6789<b>…</b></p>", html_output
40
+ end
41
+
42
+ def test_truncate_another_edge_case
43
+ html_input = "12345<p>67890<b>123456</b>7890</p>".html_safe
44
+ html_output = bento_truncate(html_input, :length => 10)
45
+ assert_equal "12345<p>67890…</p>", html_output
46
+ end
47
+
48
+ def test_truncate_html_with_separator
49
+ html_input = "12345<p>67 901234<b></p>".html_safe
50
+ html_output = bento_truncate(html_input, :length => 10, :separator => ' ')
51
+ assert_equal "12345<p>67…</p>", html_output
52
+ end
53
+
54
+ def test_truncate_html_with_separator_unavailable
55
+ html_input = "12345<p>678901234<b></p>".html_safe
56
+ html_output = bento_truncate(html_input, :length => 10, :separator => ' ')
57
+ assert_equal "12345<p>6789…</p>", html_output
58
+ end
59
+
60
+ def test_truncate_html_with_boundary_separator
61
+ # known edge case we dont' handle, sorry. If this test
62
+ # fails, that could be a good thing if you've fixed the edge case!
63
+ html_input = "12345<p>6 8<b>90123456</b>7890</p>".html_safe
64
+ html_output = bento_truncate(html_input, :length => 10, :separator => ' ')
65
+ assert_equal "12345<p>6 8<b>9…</b></p>", html_output
66
+ end
67
+
68
+
69
+
70
+
71
+ end
@@ -58,7 +58,7 @@ class EbscoHostEngineTest < ActiveSupport::TestCase
58
58
  assert_equal ["date"], query_params["sort"]
59
59
  end
60
60
 
61
- def fielded_construction
61
+ def test_fielded_construction
62
62
  url = @engine.query_url(:query => "cancer", :search_field => "SU")
63
63
 
64
64
  query_params = CGI.parse( URI.parse(url).query )
@@ -66,10 +66,33 @@ class EbscoHostEngineTest < ActiveSupport::TestCase
66
66
  assert_equal ["(SU cancer)"], query_params["query"]
67
67
  end
68
68
 
69
+ def test_peer_review_limit_construction
70
+ url = @engine.query_url(:query => "cancer", :search_field => "SU", :peer_reviewed_only => true)
71
+
72
+ query_params = CGI.parse( URI.parse(url).query )
73
+
74
+ assert_equal ["(SU cancer) AND (RV Y)"], query_params["query"]
75
+ end
76
+
77
+ def test_date_limit_construction
78
+ url = @engine.query_url(:query => "cancer", :pubyear_start => "1980", :pubyear_end => "1989")
79
+ query_params = CGI.parse( URI.parse(url).query )
80
+
81
+ assert_equal ["cancer AND (DT 1980-1989)"], query_params["query"]
82
+
83
+ # just one
84
+ url = @engine.query_url(:query => "cancer", :pubyear_start => "1980")
85
+ query_params = CGI.parse( URI.parse(url).query )
86
+
87
+ assert_equal ["cancer AND (DT 1980-)"], query_params["query"]
88
+
89
+ end
90
+
91
+
69
92
  def test_prepare_query
70
- query = @engine.ebsco_query_prepare('one :. ; two "three four" AND NOT OR five')
93
+ query = @engine.ebsco_query_prepare('one :. ; two "three four" and NOT OR five')
71
94
 
72
- assert_equal 'one AND two AND "three four" AND five', query
95
+ assert_equal 'one AND two AND "three four" AND "and" AND "NOT" AND "OR" AND five', query
73
96
  end
74
97
 
75
98
  def test_removes_paren_literals
@@ -145,5 +168,89 @@ class EbscoHostEngineTest < ActiveSupport::TestCase
145
168
  assert_present results.error[:error_info]
146
169
 
147
170
  end
171
+
172
+ test_with_cassette("fulltext info", :ebscohost) do
173
+ # We count on SOME records from first 10 for this query having fulltext,
174
+ # if you need to re-record VCR cassette and this query doesn't work
175
+ # for that anymore, then pick a different query.
176
+ results = @engine.search("cancer")
177
+
178
+ results_with_fulltext = results.find_all {|r| r.custom_data["fulltext_formats"] }
179
+
180
+ assert_present results_with_fulltext
181
+
182
+ results_with_fulltext.each do |record|
183
+ array = record.custom_data["fulltext_formats"]
184
+ # it's an array
185
+ assert_kind_of Array, array
186
+ # who's only legal values are P, T, and C, the EBSCO vocab for formats.
187
+ assert_equal array.length, array.find_all {|v| %w{P C T}.include?(v)}.length
188
+
189
+ assert record.link_is_fulltext?, "#link_is_fulltext set"
190
+ end
191
+
192
+ end
193
+
194
+ test_with_cassette("live book example", :ebscohost) do
195
+ # We keep adjusting the EBSCOHost heuristics for guessing format,
196
+ # and causing regressions, this test guards against them.
148
197
 
198
+ # This particular example from RILM is a book, but
199
+ # is getting listed as a book chapter, sort of.
200
+
201
+ engine = BentoSearch::EbscoHostEngine.new( @config.merge(:databases => ["rih"]) )
202
+
203
+ results = engine.search('"Funk: The music, the people, and the rhythm of the one"', :per_page => 1)
204
+
205
+ result = results.first
206
+
207
+ assert_equal "Book", result.format
208
+ assert_equal "St. Martin's Press", result.publisher
209
+ assert_equal "1996", result.year
210
+
211
+ assert_blank result.source_title
212
+ end
213
+
214
+ test_with_cassette("live pathological book_item example", :ebscohost) do
215
+ # this guy from RILM has really crappy metadata on EBSCO,
216
+ # but we still want to detect it as a book_item, not a book.
217
+
218
+ a = 'Heidegger and the management of the Haymarket Opera, 1713-1717'
219
+
220
+ engine = BentoSearch::EbscoHostEngine.new( @config.merge(:databases => ["rih"]) )
221
+ results = engine.search('"Heidegger and the management of the Haymarket Opera, 1713-1717"')
222
+ result = results.first
223
+
224
+ assert_equal :book_item, result.format
225
+
226
+
227
+ # for reasons I can't figure out, weird encoding in the hyphen makes us
228
+ # test start_with instead
229
+ assert result.title.starts_with?("Heidegger and the management of the Haymarket Opera, 1713")
230
+ assert result.source_title.starts_with?("Opera remade (1700")
231
+ end
232
+
233
+ test_with_cassette("dissertation example", :ebscohost) do
234
+ # yeah, all the weird ones are from RILM
235
+ engine = BentoSearch::EbscoHostEngine.new( @config.merge(:databases => ["rih"]) )
236
+
237
+ results = engine.search('"Research into free jazz in France, 1960-1975"')
238
+ result = results.first
239
+
240
+ assert_equal "Research into free jazz in France, 1960-1975", result.title
241
+ assert_equal :dissertation, result.format
242
+ end
243
+
244
+ test_with_cassette("another dissertation", :ebscohost) do
245
+ # yeah, all the weird edge cases that make good tests are from RILM, it's
246
+ # got weird data.
247
+
248
+ engine = BentoSearch::EbscoHostEngine.new( @config.merge(:databases => ["rih"]) )
249
+ results = engine.search('"Machine gun voices: Bandits, favelas, and utopia in Brazilian funk"')
250
+ result = results.first
251
+
252
+ assert_equal :dissertation, result.format
253
+ assert_equal "Machine gun voices: Bandits, favelas, and utopia in Brazilian funk", result.title
254
+ end
255
+
149
256
  end
@@ -27,18 +27,26 @@ class GoogleBooksEngineTest < ActiveSupport::TestCase
27
27
 
28
28
  assert_kind_of BentoSearch::ResultItem, first
29
29
 
30
- assert_not_empty first.title
31
- assert_not_empty first.publisher
32
- assert_not_empty first.link
33
- assert_not_empty first.format
34
- assert_not_nil first.year
35
- assert_not_empty first.abstract
36
- assert first.abstract.html_safe?
37
-
38
- assert_present first.language_code
39
-
40
- assert_not_empty first.authors
41
- assert_not_empty first.authors.first.display
30
+ assert_not_empty first.title
31
+ assert_not_empty first.publisher
32
+ assert_not_empty first.link
33
+ assert_not_empty first.format
34
+ assert_not_nil first.year
35
+ assert_not_empty first.abstract
36
+ assert first.abstract.html_safe?
37
+
38
+ assert_present first.language_code
39
+
40
+ assert_not_empty first.authors
41
+ assert_not_empty first.authors.first.display
42
+
43
+ # assume at least one thing in the result set has an ISBN to test
44
+ # our ISBN-setting code.
45
+ assert_present results.find {|r| r.isbn.present? }
46
+
47
+ assert_present first.custom_data[:viewability]
48
+
49
+ assert_not_nil first.link_is_fulltext?
42
50
  end
43
51
 
44
52
  test_with_cassette("pagination", :gbs) do
@@ -79,11 +87,11 @@ class GoogleBooksEngineTest < ActiveSupport::TestCase
79
87
  def test_sort_construction
80
88
  url = @engine.send(:args_to_search_url, :query => "cancer", :sort => "date_desc")
81
89
 
82
- assert_match '&sort=newest', url
90
+ assert_match '&orderBy=newest', url
83
91
 
84
92
  url = @engine.send(:args_to_search_url, :query => "cancer", :sort => "relevance")
85
93
 
86
- assert_not_match "&sort", url
94
+ assert_not_match "&orderBy", url
87
95
  end
88
96
 
89
97
  def test_fielded_search
@@ -81,7 +81,7 @@ class GoogleSiteSearchTest < ActiveSupport::TestCase
81
81
  assert_present first.journal_title # used as source_title for display url
82
82
 
83
83
  # no openurls for google, we decided
84
- assert_nil first.to_openurl
84
+ assert_nil BentoSearch::StandardDecorator.new(first, nil).to_openurl
85
85
  end
86
86
 
87
87
  test_with_cassette("with highlighting", :google_site) do
@@ -93,9 +93,7 @@ class GoogleSiteSearchTest < ActiveSupport::TestCase
93
93
 
94
94
  assert first.title.html_safe?
95
95
  assert first.abstract.html_safe?
96
- assert first.journal_title.html_safe?
97
-
98
- assert first.published_in.html_safe?
96
+ assert first.journal_title.html_safe?
99
97
  end
100
98
 
101
99
  test_with_cassette("without highlighting", :google_site) do
@@ -119,4 +117,13 @@ class GoogleSiteSearchTest < ActiveSupport::TestCase
119
117
  assert results.empty?
120
118
  end
121
119
 
120
+ test_with_cassette("gets format string", :google_site) do
121
+ results = @engine.search("Adobe PDF")
122
+
123
+ # assume at least one result had a PDF format, which it does
124
+ # in our current VCR capture. For a new one, find a search where
125
+ # it does.
126
+ assert_present(results.find_all {|i| i.format_str =~ /PDF/ }, "At least one result has PDF in format_str")
127
+ end
128
+
122
129
  end
@@ -3,76 +3,17 @@ require 'test_helper'
3
3
  class ItemDecoratorsTest < ActiveSupport::TestCase
4
4
  MockEngine = BentoSearch::MockEngine
5
5
 
6
- # simple decorator that replaces main link
7
- module Decorator
8
- def link
9
- "http://newlink.com"
10
- end
11
-
12
- def other_links
13
- super + [ BentoSearch::Link.new(:label => "One", :url => "http://one.com") ]
14
- end
15
- end
16
-
17
- setup do
18
- @engine = MockEngine.new(:item_decorators => [Decorator])
19
- end
20
-
21
- test "decorators" do
22
- results = @engine.search(:query => "Query")
23
-
24
- assert_present results
25
-
26
- results.each do |result|
27
- assert_kind_of Decorator, result
28
-
29
- assert_equal "http://newlink.com", result.link
30
-
31
- assert_present result.other_links
32
-
33
- assert_equal "One", result.other_links.first.label
34
- assert_equal "http://one.com", result.other_links.first.url
35
- end
36
- end
37
-
38
- test "decorator as string" do
39
- @engine = MockEngine.new(:item_decorators => ["::ItemDecoratorsTest::Decorator"])
40
-
41
- results = @engine.search(:query => "Query")
42
-
43
- results.each do |result|
44
- assert_kind_of Decorator, result
45
- end
46
6
 
47
- end
7
+ ################
8
+ # ABove here, old style decorators on their way out. Below, new:
9
+ ###########
48
10
 
49
- # Is it a good idea to have a decorator that mutates on 'extend'?
50
- # I'm not sure, I think probably not, but it is possible.
51
- # Here we'll use it to move an original link to other links
52
- module MutatingDecorator
53
- def self.extended(item)
54
- orig_link = item.link
55
-
56
- item.link = nil
57
-
58
- item.other_links << BentoSearch::Link.new(:label => "Some Other", :url => orig_link)
59
- end
60
- end
61
-
62
- test "mutating decorator" do
63
- @engine = MockEngine.new(:item_decorators => [MutatingDecorator], :link => "http://example2.org")
11
+ test "decorator specified in configuration" do
12
+ @engine = MockEngine.new(:for_display => {:decorator => "TestDecorator"})
64
13
  results = @engine.search("query")
65
14
 
66
- assert_present results
67
-
68
- results.each do |result|
69
- assert_blank result.link
70
- assert_equal "http://example2.org", result.other_links.first.url
71
- assert_equal "Some Other", result.other_links.first.label
72
- end
73
-
15
+ assert_equal "TestDecorator", results.first.decorator
74
16
  end
75
-
76
17
 
77
18
 
78
19