borrow_direct 0.9.1 → 0.10.0

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