borrow_direct 0.9.1 → 0.10.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTczYmU1OWU3ZWZkNzkwZTBhZTZmMTU0MWRjMTk1NGVhNTY3YzYxNA==
4
+ N2U0ZTMyZDhmZDA4ZTE3YWNjNDk0MTYwMDEzNTM4MTQ5MDY2NzQwOQ==
5
5
  data.tar.gz: !binary |-
6
- OTAwNmQ5OWFhZDJmZTgzM2FiYTE4NzVjOTVhMDIzNDM3MzlmOWM1Yw==
6
+ YzQ0MDdjMGIyMjYzNmU1NmZkY2Q5MzRkNTcxNWRhNjkyZTE3ZDQ3ZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZWJhOTg3Nzk5ODY5ZWU1OGE5ZDIwMjhhYjc5OGYwNmZlODNhZmMxMDMxYTlm
10
- ZmNiMGQxZTMxNTkzMDdhYWNiYTk2NDg0ODBmYTdkOTY3ZTQ4ZDcwMmQwYmUz
11
- MzIxYzVmNDBhMGRmNGE3NWNmODkyNTlhYWQ1ZjYwYmJkOGNlZWQ=
9
+ MThiMzE1MWJmNmQ3YzZiNjczYTY5MjU0OGIxYmRlOWQ5MTc5ZmQ2NzM5OTY4
10
+ MjRmMWQ5M2RjYzEzNGFjZGVlYjkwOWU2N2M3ZWFjMTM5OTFkOTk1YjEzZGM2
11
+ ZjQ5M2MwMTM4YzFlZGNjOTdmMzBlZGZjM2IwOGNjNDg5NzJhMjA=
12
12
  data.tar.gz: !binary |-
13
- YThjMDY3OWFkMzY3YWM4YTBhZGEyOTVhNzliOGM2NzgyY2VlZTMwOTZhM2M0
14
- NzM5MzViNjk4Y2E0ZTc5NzU2Yzg3NmZhYjRhNmU4YjAxYjM2MzRkYjAzYzFk
15
- MTRhNTFmMTI4Y2IzNzc4ODVkYjI4M2RlY2JmNjZmNDU2NjlkYmY=
13
+ MmZhNzUyNTk5ODhiYWI5ZjdiNjVhZThhZTdlMTgyZDM3MzdhODhlOThkZjE4
14
+ ZWNmNzg4NzkwYzFmNmQ1OTA4MDk5MTY5N2IyODAwNWJhYjg4ZDY5NjY1YzVl
15
+ NGQ1YjllODU3M2Q0M2E0MjczN2VlMzUxZTVhYmU2ZjJhODcxYTg=
data/README.md CHANGED
@@ -110,17 +110,22 @@ BorrowDirect::GenerateQuery.new.query_url_with(
110
110
  :isbn => "1234435445")
111
111
  ~~~
112
112
 
113
- Sometimes you want to generate a search for a specific known item, and use
114
- an ISBN if available, otherwise an author/title search. That is one of our
115
- own main use cases for these deep links. The `#best_known_item_query_url_with`
116
- method is available to automatically use ISBN if available, otherwise author/title.
113
+ Or specify your own query passed as a string, possibly a complex one
114
+ using BD's undocumented syntax that you figure out. Use `
115
+ BorrowDirect::GenerateQuery.escape` to escape values, but don't CGI escape the input.
117
116
 
118
- The GenerateQuery class can be enhanced if there is demand; to allow more
119
- flexible searches (instead of always phrase searches with boolean AND); to or allow
120
- sending barcode directly to BD instead of relying on a local authenticating redirect
121
- script.
117
+ ~~~ruby
118
+ query = %Q{isbn="#{BorrowDirect::GenerateQuery.escape('1212')}" and (ti="#{BorrowDirect::GenerateQuery.escape('foo')}" or ti="#{BorrowDirect::GenerateQuery.escape('bar')}")}
119
+ BorrowDirect::GenerateQuery.new.query_url_with query
120
+ ~~~
122
121
 
122
+ For the common case of doing an author-title keyword search, this gem has some suggested
123
+ normalization it applies to author and title, to maximize chances of succesful hits.
124
+ (limiting to 5 words in title, searching on main title only not subtitle, etc.)
123
125
 
126
+ ~~~ruby
127
+ BorrowDirect::GenerateQuery.new.normalized_author_title_query(:title => some_title, :author => some_author)
128
+ ~~~
124
129
 
125
130
  ### Errors
126
131
 
@@ -1,33 +1,106 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Quick and dirty script to do some testing.
4
+
3
5
  # ruby -Ilib:test ./bd_metrics/finditem_measure.rb
4
6
 
5
7
  require 'borrow_direct'
6
8
  require 'borrow_direct/find_item'
9
+ require 'date'
10
+
11
+ key = "isbn"
12
+ sourcefile = ARGV[0] || File.expand_path("../isbn-bd-test-200.txt", __FILE__)
7
13
 
8
- key = ARGV[0] || "isbn"
9
- sourcefile = ARGV[1] || File.expand_path("../#{key}.txt", __FILE__)
14
+ # How long to wait n BD before giving up.
15
+ timeout = 20
10
16
 
11
- puts "#{key}: #{sourcefile}"
17
+ # Range of how long to wait between requests, in seconds. Actual
18
+ # delay each time randomly chosen from this range.
19
+ # wait one to 7 minutes.
20
+ delay = 60..420
12
21
 
13
- identifiers = File.readlines(sourcefile).shuffle
22
+ puts "#{ENV['BD_LIBRARY_SYMBOL']}: #{key}: #{sourcefile}: #{Time.now.localtime}"
14
23
 
24
+ identifiers = File.readlines(sourcefile) #.shuffle
25
+
15
26
  puts " #{identifiers.count} total input identifiers"
16
27
 
17
- times = []
18
- errors = []
19
- finder = BorrowDirect::FindItem.new(ENV["BD_FINDITEM_PATRON"], ENV["BD_LIBRARY_SYMBOL"])
28
+ times = []
29
+ errors = []
30
+ timeouts = []
31
+
32
+ finder = BorrowDirect::FindItem.new(ENV["BD_PATRON"], ENV["BD_LIBRARY_SYMBOL"])
33
+ finder.timeout = timeout
34
+
35
+ i = 0
36
+
37
+ stats = lambda do |times|
38
+ h = OpenStruct.new
39
+
40
+ h.min = times[0]
41
+ h.tenth = times[(times.count / 10) - 1]
42
+ h.median = times[(times.count / 2) - 1]
43
+ h.seventyfifth = times[(times.count - (times.count / 4)) - 1]
44
+ h.ninetieth = times[(times.count - (times.count / 10)) - 1]
45
+ h.ninetyninth = times[(times.count - (times.count / 100)) - 1]
46
+ h.max = times[times.count - 1]
47
+
48
+ # now without the ones that were timeouts
49
+ times1 = times.find_all {|t| t < timeout || (timeout - t) <= 0.01}
50
+ h1 = OpenStruct.new
51
+ h1.min = times[0]
52
+ h1.tenth = times[(times.count / 10) - 1]
53
+ h1.median = times[(times.count / 2) - 1]
54
+ h1.seventyfifth = times[(times.count - (times.count / 4)) - 1]
55
+ h1.ninetieth = times[(times.count - (times.count / 10)) - 1]
56
+ h1.ninetyninth = times[(times.count - (times.count / 100)) - 1]
57
+ h1.max = times[times.count - 1]
58
+
59
+ return [h, h1]
60
+ end
61
+
62
+ printresults = lambda do
63
+ times.sort!
64
+
65
+ (t, t1) = stats.call(times)
66
+
67
+ puts "\n"
68
+ puts "tested #{i} identifiers, with timeout #{timeout}s, delaying #{delay} seconds between FindItem api requests\n\n"
69
+ puts "timing distribution (inc. timeouts) min: #{t.min.round(1)}s; 10th %ile: #{t.tenth.round(1)}s; median: #{t.median.round(1)}s; 75th %ile: #{t.seventyfifth.round(1)}s; 90th %ile: #{t.ninetieth.round(1)}s; 99th %ile: #{t.ninetyninth.round(1)}s; max: #{t.max.round(1)}s\n\n"
70
+ puts "timing distribution (W/O timeouts) min: #{t1.min.round(1)}s; 10th %ile: #{t1.tenth.round(1)}s; median: #{t1.median.round(1)}s; 75th %ile: #{t1.seventyfifth.round(1)}s; 90th %ile: #{t1.ninetieth.round(1)}s; 99th %ile: #{t1.ninetyninth.round(1)}s; max: #{t1.max.round(1)}s\n\n"
71
+ puts " error count: #{errors.count}"
72
+ puts " timeout count: #{timeouts.count}"
73
+ puts "\n"
74
+ end
20
75
 
21
76
 
22
77
  at_exit do
23
- puts "\n\nERRORS: "
78
+ puts "\n\n\n"
79
+
80
+ puts "Finished: #{ENV['BD_LIBRARY_SYMBOL']}: #{key}: #{sourcefile}: #{Time.now.localtime}"
81
+
82
+
83
+ puts "Finished at: #{Time.now.localtime}\n\n"
84
+
85
+ printresults.call
86
+
87
+
88
+ puts "\nAll errors: "
24
89
  errors.each do |arr|
25
90
  puts arr.inspect
26
91
  end
92
+
93
+ puts "\nAll timeouts: "
94
+ timeouts.each do |arr|
95
+ puts arr.inspect
96
+ end
97
+
98
+ puts "\n\n\n"
99
+
27
100
  end
28
101
 
29
- i = 0
30
102
  identifiers.each do |id|
103
+ print "."
31
104
  id = id.chomp
32
105
  i = i + 1
33
106
 
@@ -35,27 +108,21 @@ identifiers.each do |id|
35
108
 
36
109
  begin
37
110
  finder.find_item_request(key => id)
111
+ rescue BorrowDirect::HttpTimeoutError => e
112
+ timeouts << [key, id, e]
38
113
  rescue BorrowDirect::Error => e
39
114
  errors << [key, id, e]
40
115
  end
41
116
  elapsed = Time.now - start
42
117
 
43
118
  times << elapsed
44
- times.sort!
45
119
 
46
120
  if i % 10 == 0
47
- min = times[0]
48
- tenth = times[(times.count / 10) - 1]
49
- median = times[(times.count / 2) - 1]
50
- seventyfifth = times[(times.count - (times.count / 4)) - 1]
51
- ninetieth = times[(times.count - (times.count / 10)) - 1]
52
- ninetyninth = times[(times.count - (times.count / 100)) - 1]
53
-
54
- max = times[times.count - 1]
55
-
56
- puts "i==#{i}; min: #{min}; 10th %ile: #{tenth}; median: #{median}; 75th %ile: #{seventyfifth}; 90th %ile: #{ninetieth}; 99th %ile: #{ninetyninth}; max: #{max}"
57
- puts " errors: #{errors.count}"
121
+ printresults.call
58
122
  end
59
123
 
124
+ print "w"
125
+ sleep rand(delay)
126
+
60
127
  end
61
128
 
@@ -0,0 +1,200 @@
1
+ 9780585488363
2
+ 0140206221
3
+ 9780300070163
4
+ 9781594202919
5
+ 1904859208
6
+ 9780309255998
7
+ 9781555702212
8
+ 9781118559215
9
+ 9780898382600
10
+ 9788764300086
11
+ 0300070160
12
+ 9781435666504
13
+ 9780814725245
14
+ 9781442225459
15
+ 9780585147574
16
+ 9781118836583
17
+ 0520938038978
18
+ 0938692942
19
+ 3642034551
20
+ 9780060243913
21
+ 9781571686565
22
+ 9781604653212
23
+ 9780585048529
24
+ 9780783219097
25
+ 9780231520980
26
+ 9781886449350
27
+ 9780634090608
28
+ 9780062503404
29
+ 9781742980676
30
+ 160486270X
31
+ 9780309212861
32
+ 9783540737087
33
+ 0486122387
34
+ 9780671869229
35
+ 9783540644101
36
+ 9781563084812
37
+ 9780435270728
38
+ 9780721601892
39
+ 9780805015959
40
+ 074533086X
41
+ 9780415882293
42
+ 9780314190826
43
+ 9780781782029
44
+ 9780292759367
45
+ 9781593764944
46
+ 9780470873908
47
+ 187317683X
48
+ 9780375856525
49
+ 9781555841973
50
+ 9781933115603
51
+ 9780813506272
52
+ 9781420081886
53
+ 9780312331597
54
+ 9780790739250
55
+ 0853451753
56
+ 9780470740958
57
+ 9780840032980
58
+ 9781491900987
59
+ 9781605537665
60
+ 9780066211312
61
+ 9780585025117
62
+ 9780375830730
63
+ 9780316608459
64
+ 0333914554
65
+ 9780804717625
66
+ 1904859062
67
+ 9780817644222
68
+ 9780060234836
69
+ 9780333914557
70
+ 9780470578827
71
+ 9781401202019
72
+ 9781619426344
73
+ 0631165746
74
+ 9780977696611
75
+ 9786310335841
76
+ 9780470584330
77
+ 9781401213510
78
+ 0142002429
79
+ 9781118173091
80
+ 9781905222759
81
+ 9780517701010
82
+ 9782207261163
83
+ 9780226293516
84
+ 0271028890
85
+ 9781604860641
86
+ 0520938038
87
+ 9780670891573
88
+ 0521891574
89
+ 9780807084939
90
+ 3531050850
91
+ 1885923902
92
+ 0470019352
93
+ 2711823903
94
+ 9780804776714
95
+ 0195167643
96
+ 2902524412
97
+ 0300043910
98
+ 0231051964
99
+ 9789652351487
100
+ 0299158101
101
+ 0313221588
102
+ 9780881326727
103
+ 9781892127303
104
+ 0444820914
105
+ 0109578805
106
+ 9042918713
107
+ 9780415604017
108
+ 9783834936011
109
+ 9781420069129
110
+ 0825672783
111
+ 9789048139668
112
+ 9788896319109
113
+ 0582368863
114
+ 9780203833001
115
+ 0500018421
116
+ 0761920846
117
+ 9781455727551
118
+ 9781118418420
119
+ 9780132119856
120
+ 1741144485
121
+ 9781480328488
122
+ 9781443842273
123
+ 0882756702
124
+ 2070112713
125
+ 9789799101204
126
+ 9783770544974
127
+ 0205200095
128
+ 1560003383
129
+ 0205510701
130
+ 9789251053003
131
+ 9780415558938
132
+ 014004227X
133
+ 0766846822
134
+ 9780415197342
135
+ 0804737088
136
+ 1878822624
137
+ 9004104062
138
+ 41062001
139
+ 9788131775660
140
+ 1604246014
141
+ 9780801890048
142
+ 0195208846
143
+ 1884585388
144
+ 1555835589
145
+ 3540651160
146
+ 041505818X
147
+ 9287140553
148
+ 0394538579
149
+ 9780754628309
150
+ 1566706688
151
+ 3487072157
152
+ 0226323145
153
+ 9781559363303
154
+ 0801438136
155
+ 0787964344
156
+ 9780807834343
157
+ 0195300831
158
+ 0323081665
159
+ 9789814345477
160
+ 3900627002
161
+ 0393977757
162
+ 0801831008
163
+ 0844407291
164
+ 0582418666
165
+ 0863583016
166
+ 9786074620191
167
+ 0679750533
168
+ 1569730431
169
+ 1419551329
170
+ 1405116994
171
+ 080532402X
172
+ 8879890913
173
+ 9781616690106
174
+ 8187586370
175
+ 9789004255265
176
+ 0205482252
177
+ 0300049447
178
+ 8772884746
179
+ 0807824682
180
+ 9992790768
181
+ 9781598743036
182
+ 0754655334
183
+ 9783034312028
184
+ 185437124X
185
+ 9781439811283
186
+ 9781412984652
187
+ 9781579548896
188
+ 0131975412
189
+ 9781437720808
190
+ 9781409469056
191
+ 0816188734
192
+ 9781629142708
193
+ 027598169X
194
+ 9780801452581
195
+ 0596008678
196
+ 0521855764
197
+ 1934121320
198
+ 0721679048
199
+ 0824708709
200
+ 9781606230220
@@ -14,7 +14,7 @@ module BorrowDirect
14
14
  # BorrowDirect::Defaults.find_item_patron_barcode = "99999999999"
15
15
  class Defaults
16
16
  TEST_API_BASE = "https://bdtest.relais-host.com/"
17
- PRODUCTION_API_BASE = "NOT_YET_AVAILABLE"
17
+ PRODUCTION_API_BASE = "https://borrow-direct.relaisd2d.com/"
18
18
 
19
19
  TEST_HTML_BASE = "https://bdtest.relaisd2d.com/service-proxy?command=query"
20
20
  PRODUCTION_HTML_BASE = "https://borrow-direct.relaisd2d.com/service-proxy?command=query"
@@ -118,15 +118,23 @@ module BorrowDirect
118
118
  end
119
119
 
120
120
  # Items that are available locally, and thus not requestable via BD, can
121
- # only be found by looking at the RequestMessage, bah
122
- h = response_hash["Item"]["RequestLink"]
123
- if h && h["RequestMessage"] == "This item is available locally"
121
+ # only be found by looking at the RequestMessage, bah
122
+ if locally_available?
124
123
  return false
125
124
  end
126
125
 
127
126
  return response_hash["Item"]["Available"].to_s == "true"
128
127
  end
129
128
 
129
+ # BD thinks the item is locally available at patron's home library,
130
+ # and it is not requestable for that reason.
131
+ # Items that are available locally, and thus not requestable via BD, can
132
+ # only be found by looking at the RequestMessage, bah
133
+ def locally_available?
134
+ h = response_hash["Item"]["RequestLink"]
135
+ return !! (h && h["RequestMessage"] == "This item is available locally")
136
+ end
137
+
130
138
  # Returns the AuthorizationID returned by FindItem API call,
131
139
  # or nil if none is available. Nil _can_ be returned, for
132
140
  # instance when BD returns a NotFound error instead of a good
@@ -20,7 +20,7 @@ module BorrowDirect
20
20
  self.url_base = (url_base || BorrowDirect::Defaults.html_base_url)
21
21
  end
22
22
 
23
- # query_with(:title => "one two", :author => "three four")
23
+ # build_query_with(:title => "one two", :author => "three four")
24
24
  # valid keys are those supported by BD HTML interface:
25
25
  # :title, :author, :isbn, :subject, :keyword, :isbn, :issn
26
26
  #
@@ -28,7 +28,7 @@ module BorrowDirect
28
28
  # fields are always 'and'd. We may enhance/expand later.
29
29
  #
30
30
  # Returns an un-escaped query, still needs to be put into a URL
31
- def query_with(options)
31
+ def build_query_with(options)
32
32
  clauses = []
33
33
 
34
34
  options.each_pair do |field, value|
@@ -38,43 +38,98 @@ module BorrowDirect
38
38
 
39
39
  raise ArgumentError.new("Don't recognize field code `#{field}`") unless code
40
40
 
41
- clauses << %Q{#{code}="#{escape_query_value value}"}
41
+ clauses << %Q{#{code}="#{escape value}"}
42
42
  end
43
43
 
44
44
  return clauses.join(" and ")
45
45
  end
46
46
 
47
- # Pass in :title, :author, :isbn, etc -- if we have an isbn or issn,
48
- # we'll use that alone, otherwise we'll use title and author
49
- def best_known_item_query_with(options)
50
- if options[:isbn]
51
- return query_with(options.dup.delete_if {|k| k != :isbn})
52
- elsif options[:issn]
53
- return query_with(options.dup.delete_if {|k| k != :issn})
54
- else
55
- return query_with options
56
- end
57
- end
58
47
 
59
- def query_url_with(options)
60
- query = query_with(options)
61
48
 
49
+ def query_url_with(arg)
50
+ query = arg.kind_of?(Hash) ? build_query_with(arg) : arg.to_s
51
+
62
52
  return add_query_param(self.url_base, "query", query).to_s
53
+ end
54
+
55
+ # Give it a :title and optionally an :author, we'll normalize
56
+ # them to our suggestion for a good BD author-title keyword
57
+ # search. Returns a hash suitable for passing to #query_url_with
58
+ #
59
+ # Additional option :max_title_words, default 5.
60
+ def normalized_author_title_params(options)
61
+ raise ArgumentError.new("Need a Hash argument, got #{options.inspect}") unless options.kind_of?(Hash)
62
+
63
+ # Symbolize keys please
64
+ #options = options.inject({}){ |h, (k, v)| hash.merge( (k.respond_to?(:to_sym) ? k.to_sym : k) => v ) }
65
+
66
+ title = options[:title].dup if options[:title]
67
+ author = options[:author].dup if options[:author]
68
+
69
+ # We don't do any normalization to author at present.
70
+ title = normalized_title(title, :max_title_words => options[:max_title_words])
71
+
72
+ results = {}
73
+ results[:title] = title if title && ! title.empty?
74
+ results[:author] = author if author && ! author.empty?
75
+
76
+ return results
77
+ end
63
78
 
79
+ # :title, :author and optionally :max_title_words.
80
+ #
81
+ # Returns a query with a suggested normalized author and title
82
+ # for best BD search. May return just BD base URL if no author/title
83
+ # given.
84
+ def normalized_author_title_query(options)
85
+ return self.query_url_with self.normalized_author_title_params(options)
64
86
  end
65
87
 
66
- def best_known_item_query_url_with(options)
67
- query = best_known_item_query_with(options)
88
+ def normalized_title(title, args = {})
89
+ return "" if title.nil? || title.empty?
68
90
 
69
- return add_query_param(self.url_base, "query", query).to_s
91
+ max_title_words = args[:max_title_words] || 5
92
+
93
+ # Remove all things in parens at the END of the title, they tend
94
+ # to be weird addendums.
95
+ title.gsub!(/\([^)]*\)\s*$/, '')
96
+
97
+ # Strip the subtitle or other weird titles, just keep the text
98
+ # before the first colon OR semi-colon
99
+ title.sub!(/[\:\;](.*)$/, '')
100
+
101
+ # remove any remaining non-alphanumeric, excepting apostrophe, replacing
102
+ # with space. The punctuation doesn't help our queries.
103
+ title.gsub!(/[^[:alnum:][:space:]\']/, ' ')
104
+
105
+ # compress any remaining whitespace
106
+ title.strip!
107
+ title.gsub!(/\s+/, ' ')
108
+
109
+ # downcase
110
+ title.downcase!
111
+
112
+ # Limit to only first N words
113
+ if max_title_words && title.index(/((.+?[ ,.:\;]+){#{max_title_words}})/)
114
+ title = title.slice(0, $1.length).gsub(/[ ,.:\;]+$/, '')
115
+ end
116
+
117
+ return title
70
118
  end
71
119
 
120
+
121
+
122
+ # Escape a query value.
72
123
  # We don't really know how to escape, for now
73
124
  # we just remove double quotes and parens, and replace with spaces.
74
125
  # those seem to cause problems, and that seems to work.
75
- def escape_query_value(str)
126
+ def self.escape(str)
76
127
  str.gsub(/[")()]/, ' ')
77
128
  end
129
+ # Instance method version for convenience.
130
+ def escape(str)
131
+ self.class.escape(str)
132
+ end
78
133
 
79
134
  def add_query_param(uri, key, value)
80
135
  uri = URI.parse(uri) unless uri.kind_of? URI
@@ -100,9 +100,9 @@ module BorrowDirect
100
100
 
101
101
 
102
102
  return response_hash
103
- rescue HTTPClient::ReceiveTimeoutError => e
103
+ rescue HTTPClient::ReceiveTimeoutError, HTTPClient::ConnectTimeoutError, HTTPClient::SendTimeoutError => e
104
104
  elapsed = Time.now - start_time
105
- raise BorrowDirect::HttpTimeoutError.new("Timeout after #{elapsed}s connecting to BorrowDirect server at #{@api_base}")
105
+ raise BorrowDirect::HttpTimeoutError.new("Timeout after #{elapsed.round(1)}s connecting to BorrowDirect server at #{@api_base}")
106
106
  end
107
107
 
108
108
  def http_client
@@ -1,3 +1,3 @@
1
1
  module BorrowDirect
2
- VERSION = "0.9.1"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -121,6 +121,10 @@ describe "FindItem", :vcr do
121
121
  assert_equal false, @find_item.find(:isbn => @locally_avail_item_isbn).requestable?
122
122
  end
123
123
 
124
+ it "knows locally_available?" do
125
+ assert_equal true, @find_item.find(:isbn => @locally_avail_item_isbn).locally_available?
126
+ end
127
+
124
128
  it "not requestable for item that does not exist in BD" do
125
129
  assert_equal false, @find_item.find(:isbn => "NO_SUCH_THING").requestable?
126
130
  end
@@ -1,3 +1,5 @@
1
+ # Encoding: UTF-8
2
+
1
3
  require 'test_helper'
2
4
  require 'uri'
3
5
  require 'cgi'
@@ -61,66 +63,67 @@ describe "GenerateQuery" do
61
63
  end
62
64
  end
63
65
 
64
- describe "best_known_item_query_url_with" do
65
- it "uses only isbn when available" do
66
+ describe "with string arg" do
67
+ it "generates supplied query" do
66
68
  generate_query = BorrowDirect::GenerateQuery.new(@test_base)
67
69
 
68
- url = generate_query.best_known_item_query_url_with(:isbn => "OUR_ISBN", :title => "This is a title", :author => "This is an author")
70
+ query = %Q{isbn="#{BorrowDirect::GenerateQuery.escape('1212')}" and (ti="#{BorrowDirect::GenerateQuery.escape('foo')}" or ti="#{BorrowDirect::GenerateQuery.escape('bar')}")}
69
71
 
70
- assert url.start_with? @test_base
72
+ url = generate_query.query_url_with(query)
71
73
 
72
74
  parsed_url = URI.parse(url)
73
75
  url_query = CGI.parse( parsed_url.query )
74
-
75
76
  assert_present url_query
76
-
77
77
  assert_length 1, url_query["query"]
78
78
 
79
- query_text = url_query["query"].first
80
-
81
- parts = query_text.split(" and ")
82
-
83
- assert_length 1, parts
84
-
85
- assert_include parts, 'isbn="OUR_ISBN"'
79
+ assert_equal query, url_query["query"].first
86
80
  end
81
+ end
87
82
 
88
- it "uses author and title when it has to" do
89
- generate_query = BorrowDirect::GenerateQuery.new(@test_base)
90
-
91
- url = generate_query.best_known_item_query_url_with(:title => "This is a title", :author => "This is an author")
92
-
93
- assert url.start_with? @test_base
94
-
95
- parsed_url = URI.parse(url)
96
- url_query = CGI.parse( parsed_url.query )
83
+ describe "#normalized_author_title_params" do
84
+ before do
85
+ @generator = BorrowDirect::GenerateQuery.new(@test_base)
86
+ end
87
+ it "raises without good arguments" do
88
+ assert_raises(ArgumentError) {@generator.normalized_author_title_params(nil)}
89
+ end
97
90
 
98
- assert_present url_query
91
+ it "passes through simple author and title" do
92
+ author ="John Smith"
93
+ title = "Some Book"
94
+ assert_equal( {:title => "some book", :author => author}, @generator.normalized_author_title_params(:author => author, :title => title))
95
+ end
99
96
 
100
- assert_length 1, url_query["query"]
97
+ it "works with just a title" do
98
+ title = "Some Book"
99
+ expected = {:title => "some book"}
100
+ assert_equal expected, @generator.normalized_author_title_params(:title => title)
101
+ assert_equal expected, @generator.normalized_author_title_params(:title => title, :author => nil)
102
+ assert_equal expected, @generator.normalized_author_title_params(:title => title, :author => "")
103
+ end
101
104
 
102
- query_text = url_query["query"].first
105
+ it "title remove trailing parens" do
106
+ title = "A Book (really bad one)"
103
107
 
104
- parts = query_text.split(" and ")
108
+ assert_equal( {:title => "a book"}, @generator.normalized_author_title_params(:title => title))
109
+ end
105
110
 
106
- assert_length 2, parts
111
+ it "title strip subtitles" do
112
+ assert_equal({:title => "a book"}, @generator.normalized_author_title_params(:title => "A Book: Subtitle"))
113
+ assert_equal({:title => "a book"}, @generator.normalized_author_title_params(:title => "A Book; and more"))
114
+ end
107
115
 
108
- assert_include parts, 'ti="This is a title"'
109
- assert_include parts, 'au="This is an author"'
116
+ it "limit to first 5 words" do
117
+ assert_equal({:title => "one two's three four five"}, @generator.normalized_author_title_params(:title => "One Two's Three Four Five Six Seven"))
110
118
  end
111
119
 
112
- it "can handle nil arguments" do
113
- url = BorrowDirect::GenerateQuery.new(@html_query_base_url).best_known_item_query_url_with(
114
- :isbn => nil,
115
- :title => 'the new international economic order',
116
- :author => nil
117
- )
120
+ it "okay with unicode, strip punct" do
121
+ assert_equal({:title => "el revolución"}, @generator.normalized_author_title_params(:title => "El Revolución!: Cuban poster art"))
122
+ end
118
123
 
124
+ it "full normalized_author_title_query" do
125
+ url = @generator.normalized_author_title_query(:title => "A Book: Subtitle", :author => "Smith, John" )
119
126
  query = assert_bd_query_url(url)
120
-
121
- parts = query.split(" and ")
122
-
123
- assert_include parts, 'ti="the new international economic order"'
124
127
  end
125
128
 
126
129
  end
data/test/request_test.rb CHANGED
@@ -76,6 +76,35 @@ describe "Request", :vcr => {:tag => :bd_request} do
76
76
  assert_equal 5, http_client.connect_timeout
77
77
  end
78
78
 
79
+ it "raises exception on timeout, live" do
80
+ request = {
81
+ "PartnershipId" => "BD",
82
+ "Credentials" => {
83
+ "LibrarySymbol" => VCRFilter[:bd_library_symbol],
84
+ "Barcode" => VCRFilter[:bd_patron]
85
+ },
86
+ "ExactSearch" => [
87
+ {
88
+ "Type" => "ISBN",
89
+ "Value" => @successful_item_isbn
90
+ }
91
+ ]
92
+ }
93
+ bd = BorrowDirect::Request.new("/dws/item/available")
94
+ # tiny timeout, it'll def timeout, and on connect no less
95
+ bd.timeout = 0.00001
96
+ assert_raises(BorrowDirect::HttpTimeoutError) do
97
+ response = bd.request( request )
98
+ end
99
+
100
+ # little bit longer to get maybe a receive timeout instead
101
+ bd = BorrowDirect::Request.new("/dws/item/available")
102
+ bd.timeout = 0.10
103
+ assert_raises(BorrowDirect::HttpTimeoutError) do
104
+ response = bd.request( request )
105
+ end
106
+ end
107
+
79
108
  describe "with expected errors" do
80
109
  it "still returns result" do
81
110
  request = {
@@ -17,7 +17,7 @@ end
17
17
  def assert_include(collection, item, msg = nil)
18
18
  assert_respond_to(collection, :include?, "The collection must respond to :include?.")
19
19
 
20
- msg ||= "#{collection.inspect.slice(0,10)} expected to include\#{item}"
20
+ msg ||= "#{collection.inspect} expected to include #{item.inspect}"
21
21
 
22
22
  assert collection.include?(item), msg
23
23
  end
@@ -0,0 +1,49 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://bdtest.relais-host.com/dws/item/available
6
+ body:
7
+ encoding: UTF-8
8
+ string: ! '{"PartnershipId":"BD","Credentials":{"LibrarySymbol":"DUMMY_BD_LIBRARY_SYMBOL","Barcode":"DUMMY_BD_PATRON"},"ExactSearch":[{"Type":"ISBN","Value":"0745649890"}]}'
9
+ headers:
10
+ User-Agent:
11
+ - ruby borrow_direct gem 0.9.1 (HTTPClient 2.5.3.3) https://github.com/jrochkind/borrow_direct
12
+ Accept:
13
+ - ! '*/*'
14
+ Date:
15
+ - Wed, 17 Dec 2014 16:29:43 GMT
16
+ Content-Type:
17
+ - application/json
18
+ Accept-Language:
19
+ - en
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ Server:
26
+ - nginx/1.4.1
27
+ Date:
28
+ - Wed, 17 Dec 2014 16:30:04 GMT
29
+ Content-Type:
30
+ - application/json;charset=UTF-8
31
+ Transfer-Encoding:
32
+ - chunked
33
+ Connection:
34
+ - keep-alive
35
+ body:
36
+ encoding: US-ASCII
37
+ string: ! '{"Item":{"Available":true,"SearchTerm":"isbn=0745649890","AuthorizationId":"OzHkQ_bUILh1Py-UdMtMRcbOnK0","RequestLink":{"RequestMessage":"This
38
+ item is available locally"},"PickupLocations":{"PickupLocation":["*Milton
39
+ S. Eisenhower Library","APL - EP STUDENTS - Ed Center","APL STAFF - Room 5-17","Arthur
40
+ Friedheim Library","Columbia Center","Harbor East","Harrison Medical Library-Bayview","Institute
41
+ of the History of Medicine","JH Medical Campus Office (faculty and staff)","JHMI
42
+ Pickup - Armstrong 306","JHMI Pickup - BSPH E4643","JHMI Pickup - Hampton
43
+ House 9th floor","JHMI Pickup - PCTB 115-116","JHMI Pickup - School of Nursing
44
+ 313","JHU Homewood Office (faculty and grad students)","Montgomery Library
45
+ Resource Center","SAIS Library","Washington Library Resource Center","Welch
46
+ Medical Library","Wilmer Friedenwald Library"]}}}'
47
+ http_version:
48
+ recorded_at: Wed, 17 Dec 2014 16:30:04 GMT
49
+ recorded_with: VCR 2.9.3
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: borrow_direct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-05 00:00:00.000000000 Z
11
+ date: 2015-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -113,6 +113,7 @@ files:
113
113
  - README.md
114
114
  - Rakefile
115
115
  - bd_metrics/finditem_measure.rb
116
+ - bd_metrics/isbn-bd-test-200.txt
116
117
  - bd_metrics/isbn.txt
117
118
  - bd_metrics/lccn.txt
118
119
  - bd_metrics/oclc.txt
@@ -155,6 +156,7 @@ files:
155
156
  - test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml
156
157
  - test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml
157
158
  - test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml
159
+ - test/vcr_cassettes/FindItem/find_with_Response/knows_locally_available_.yml
158
160
  - test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml
159
161
  - test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml
160
162
  - test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml
@@ -204,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
206
  version: '0'
205
207
  requirements: []
206
208
  rubyforge_project:
207
- rubygems_version: 2.4.1
209
+ rubygems_version: 2.4.4
208
210
  signing_key:
209
211
  specification_version: 4
210
212
  summary: Ruby tools for interacting with the Borrow Direct consortial services
@@ -236,6 +238,7 @@ test_files:
236
238
  - test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml
237
239
  - test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml
238
240
  - test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml
241
+ - test/vcr_cassettes/FindItem/find_with_Response/knows_locally_available_.yml
239
242
  - test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml
240
243
  - test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml
241
244
  - test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml