borrow_direct 0.9.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 +15 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +183 -0
- data/Rakefile +11 -0
- data/bd_metrics/finditem_measure.rb +61 -0
- data/bd_metrics/isbn.txt +1088 -0
- data/bd_metrics/lccn.txt +1668 -0
- data/bd_metrics/oclc.txt +167 -0
- data/borrow_direct.gemspec +30 -0
- data/lib/borrow_direct/authentication.rb +55 -0
- data/lib/borrow_direct/defaults.rb +38 -0
- data/lib/borrow_direct/error.rb +17 -0
- data/lib/borrow_direct/find_item.rb +149 -0
- data/lib/borrow_direct/generate_query.rb +78 -0
- data/lib/borrow_direct/request.rb +185 -0
- data/lib/borrow_direct/request_item.rb +119 -0
- data/lib/borrow_direct/request_query.rb +124 -0
- data/lib/borrow_direct/util.rb +18 -0
- data/lib/borrow_direct/version.rb +3 -0
- data/lib/borrow_direct.rb +19 -0
- data/test/authentication_test.rb +79 -0
- data/test/find_item_test.rb +159 -0
- data/test/generate_query_test.rb +91 -0
- data/test/request_item_test.rb +109 -0
- data/test/request_query_test.rb +113 -0
- data/test/request_test.rb +141 -0
- data/test/support/assertions.rb +23 -0
- data/test/support/vcr_filter.rb +45 -0
- data/test/test_helper.rb +39 -0
- data/test/util_test.rb +32 -0
- data/test/vcr_cassettes/Authentication/Makes_a_request_succesfully.yml +52 -0
- data/test/vcr_cassettes/Authentication/Raises_for_bad_library_symbol.yml +52 -0
- data/test/vcr_cassettes/Authentication/Raises_for_bad_patron_barcode.yml +53 -0
- data/test/vcr_cassettes/Authentication/get_auth_id/raises_for_a_bad_library_symbol.yml +52 -0
- data/test/vcr_cassettes/Authentication/get_auth_id/raises_for_a_bad_patron_barcode.yml +53 -0
- data/test/vcr_cassettes/Authentication/get_auth_id/returns_an_auth_id_for_a_good_request.yml +52 -0
- data/test/vcr_cassettes/Authentication/raw_request_to_verify_HTTP_api/.yml +52 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/finds_a_locally_available_item.yml +49 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/finds_a_requestable_item.yml +49 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/finds_an_item_that_does_not_exist_in_BD.yml +50 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/with_expected_error_PUBFI002/returns_result.yml +40 -0
- data/test/vcr_cassettes/FindItem/_find_item_request/works_with_multiple_values.yml +49 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/has_an_auth_id.yml +49 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml +40 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml +40 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml +49 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml +40 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml +50 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml +50 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_locally_available_item.yml +49 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/requestable_for_requestable_item.yml +49 -0
- data/test/vcr_cassettes/FindItem/find_with_Response/requestable_with_multiple_items_if_at_least_one_is_requestable.yml +49 -0
- data/test/vcr_cassettes/Request/authentication_id/automatically_fetches_one_when_needed.yml +52 -0
- data/test/vcr_cassettes/Request/authentication_id/can_refetch_when_instructed.yml +52 -0
- data/test/vcr_cassettes/Request/can_make_a_succesful_request.yml +49 -0
- data/test/vcr_cassettes/Request/gets_BD_error_info.yml +41 -0
- data/test/vcr_cassettes/Request/raises_on_bad_path.yml +53 -0
- data/test/vcr_cassettes/Request/raises_on_bad_request_hash.yml +63 -0
- data/test/vcr_cassettes/Request/with_expected_errors/still_returns_result.yml +41 -0
- data/test/vcr_cassettes/RequestItem/make_request/make_request_for_a_locally_available_item.yml +90 -0
- data/test/vcr_cassettes/RequestItem/make_request/make_request_for_a_requestable_item.yml +89 -0
- data/test/vcr_cassettes/RequestItem/make_request/make_request_for_an_unrequestable_item.yml +91 -0
- data/test/vcr_cassettes/RequestItem/make_request/raises_for_unrequestable.yml +91 -0
- data/test/vcr_cassettes/RequestItem/make_request/returns_number_for_succesful_request.yml +89 -0
- data/test/vcr_cassettes/RequestItem/make_request/says_no_for_item_that_BD_returns_PUBRI004.yml +89 -0
- data/test/vcr_cassettes/RequestItem/raw_requests_an_unrequestable_item.yml +91 -0
- data/test/vcr_cassettes/RequestItem/uses_manually_set_auth_id.yml +89 -0
- data/test/vcr_cassettes/RequestItem/with_pickup_location_and_requestable_item/still_works.yml +90 -0
- data/test/vcr_cassettes/RequestQuery/raw_request_query_request/returns_results.yml +381 -0
- data/test/vcr_cassettes/RequestQuery/raw_request_to_verify_the_BD_HTTP_API.yml +381 -0
- data/test/vcr_cassettes/RequestQuery/requests/fetches_default_records.yml +384 -0
- data/test/vcr_cassettes/RequestQuery/requests/fetches_full_records.yml +481 -0
- data/test/vcr_cassettes/top_level_describe/an_inner_describe/.yml +76 -0
- metadata +262 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'httpclient'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require 'borrow_direct'
|
6
|
+
|
7
|
+
module BorrowDirect
|
8
|
+
# Generic abstract BD request, put in a Hash request body, get
|
9
|
+
# back a Hash answer.
|
10
|
+
#
|
11
|
+
# response_hash = Request.new("/path/to/endpoint").request(request_hash)
|
12
|
+
#
|
13
|
+
# Typically, clients will use various sub-classes of Request implementing
|
14
|
+
# calling of individual BD API's
|
15
|
+
#
|
16
|
+
# ## AuthenticationID's
|
17
|
+
#
|
18
|
+
# Some API endpoints require an "AId"/"AuthencationID". BorrowDirect::Request
|
19
|
+
# provides some facilities for managing obtaining such (using Authentication API),
|
20
|
+
# usually will be used under the hood by Request subclasses.
|
21
|
+
#
|
22
|
+
# # fetch new auth ID using Authentication API, store it
|
23
|
+
# # in self.auth_id
|
24
|
+
# request.fetch_auth_id!(barcode, library_symbol)
|
25
|
+
#
|
26
|
+
# # return the existing value in self.auth_id, or if
|
27
|
+
# # nil run fetch_auth_id! to fill it out.
|
28
|
+
# request.need_auth_id(barcode, library_symbol)
|
29
|
+
#
|
30
|
+
# request.auth_id # cached or nil
|
31
|
+
class Request
|
32
|
+
attr_writer :http_client
|
33
|
+
attr_accessor :timeout
|
34
|
+
attr_accessor :auth_id
|
35
|
+
# default :post, but can be set to :get, usually by a subclass
|
36
|
+
attr_accessor :http_method
|
37
|
+
attr_reader :last_request_uri, :last_request_json, :last_request_response, :last_request_time
|
38
|
+
|
39
|
+
# Usually an error code from the server will be turned into an exception.
|
40
|
+
# But if there are error codes you expect (usually fixed in a subclass of Request),
|
41
|
+
# fill them in this array, and the responses will be returned anyway -- warning,
|
42
|
+
# REGARDLESS of HTTP response status code, as these are often non-200 but we want
|
43
|
+
# to catch em anyway.
|
44
|
+
attr_accessor :expected_error_codes
|
45
|
+
|
46
|
+
def initialize(path)
|
47
|
+
@api_base = Defaults.api_base
|
48
|
+
@api_path = path
|
49
|
+
|
50
|
+
@api_uri = @api_base.chomp("/") + @api_path
|
51
|
+
|
52
|
+
@expected_error_codes = []
|
53
|
+
|
54
|
+
@timeout = Defaults.timeout
|
55
|
+
@http_method = :post
|
56
|
+
end
|
57
|
+
|
58
|
+
def request(hash)
|
59
|
+
http = http_client
|
60
|
+
|
61
|
+
|
62
|
+
# Mostly for debugging, store these
|
63
|
+
@last_request_uri = @api_uri
|
64
|
+
|
65
|
+
|
66
|
+
start_time = Time.now
|
67
|
+
|
68
|
+
if self.http_method == :post
|
69
|
+
@last_request_json = json_request = JSON.generate(hash)
|
70
|
+
http_response = http.post @api_uri, json_request, self.request_headers
|
71
|
+
elsif self.http_method == :get
|
72
|
+
@last_request_query_params = hash
|
73
|
+
http_response = http.get @api_uri, hash, self.request_headers
|
74
|
+
else
|
75
|
+
raise ArgumentError.new("BorrowDirect::Request only understands http_method :get and :post, not `#{self.http_method}`")
|
76
|
+
end
|
77
|
+
|
78
|
+
@last_request_response = http_response
|
79
|
+
@last_request_time = Time.now - start_time
|
80
|
+
|
81
|
+
response_hash = begin
|
82
|
+
JSON.parse(http_response.body)
|
83
|
+
rescue JSON::ParserError => json_parse_exception
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# will be nil if we have none
|
88
|
+
einfo = error_info(response_hash)
|
89
|
+
expected_error = (einfo && self.expected_error_codes.include?(einfo.number))
|
90
|
+
|
91
|
+
|
92
|
+
if einfo && (! expected_error)
|
93
|
+
raise BorrowDirect::Error.new(einfo.message, einfo.number)
|
94
|
+
elsif http_response.code != 200 && (! expected_error)
|
95
|
+
raise BorrowDirect::HttpError.new("HTTP Error: #{http_response.code}: #{http_response.body}")
|
96
|
+
elsif response_hash.nil?
|
97
|
+
raise BorrowDirect::Error.new("Could not parse expected JSON response: #{http_response.code} #{json_parse_exception}: #{http_response.body}")
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
return response_hash
|
103
|
+
rescue HTTPClient::ReceiveTimeoutError => e
|
104
|
+
elapsed = Time.now - start_time
|
105
|
+
raise BorrowDirect::HttpTimeoutError.new("Timeout after #{elapsed}s connecting to BorrowDirect server at #{@api_base}")
|
106
|
+
end
|
107
|
+
|
108
|
+
def http_client
|
109
|
+
@http_client ||= make_http_client!
|
110
|
+
end
|
111
|
+
|
112
|
+
# For now, we can send same request headers for all requests. May have to
|
113
|
+
# make parameterized later.
|
114
|
+
# Note SOME but not all BD API endpoints REQUIRE User-Agent and
|
115
|
+
# Accept-Language (for no discernable reason)
|
116
|
+
#
|
117
|
+
# NOTE WELL: API sometimes requires User-Agent _not to change_ when using
|
118
|
+
# an AuthorizationID, or it will revoke your authorization. Need to use the
|
119
|
+
# same User-Agent when using an auth_id as you used when receiving it.
|
120
|
+
def request_headers
|
121
|
+
{ "Content-Type" => "application/json",
|
122
|
+
"User-Agent" => "ruby borrow_direct gem #{BorrowDirect::VERSION} (HTTPClient #{HTTPClient::VERSION}) https://github.com/jrochkind/borrow_direct",
|
123
|
+
"Accept-Language" => "en"
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Fetches new authID, stores it in self.auth_id, overwriting
|
128
|
+
# any previous value there. Will raise BorrowDirect::Error if no auth
|
129
|
+
# could be fetched.
|
130
|
+
#
|
131
|
+
# returns auth_id too.
|
132
|
+
def fetch_auth_id!(barcode, library_symbol)
|
133
|
+
auth = Authentication.new(barcode, library_symbol)
|
134
|
+
# use the same HTTPClient so we use the same HTTP connection, perhaps
|
135
|
+
# slightly more performance worth a shot.
|
136
|
+
auth.http_client = http_client
|
137
|
+
self.auth_id = auth.get_auth_id
|
138
|
+
end
|
139
|
+
|
140
|
+
# Will use value in self.auth_id, or if nil will
|
141
|
+
# fetch a value with fetch_auth_id! and return that.
|
142
|
+
def need_auth_id(barcode, library_symbol)
|
143
|
+
self.auth_id || fetch_auth_id!(barcode, library_symbol)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Can be used to set an already existing AuthID to be used.
|
147
|
+
# Beware, we have no facility for rescuing from escpired auth ids
|
148
|
+
# at the moment.
|
149
|
+
def with_auth_id(aid)
|
150
|
+
self.auth_id = aid
|
151
|
+
return self
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
protected
|
158
|
+
|
159
|
+
def make_http_client!
|
160
|
+
http = HTTPClient.new
|
161
|
+
if self.timeout
|
162
|
+
http.send_timeout = self.timeout
|
163
|
+
http.connect_timeout = self.timeout
|
164
|
+
http.receive_timeout = self.timeout
|
165
|
+
end
|
166
|
+
|
167
|
+
return http
|
168
|
+
end
|
169
|
+
|
170
|
+
# returns an OpenStruct with #message and #number,
|
171
|
+
# or nil if error info can not be extracted
|
172
|
+
def error_info(hash)
|
173
|
+
if hash && (e = hash["Error"]) && (e["ErrorNumber"] || e["ErrorMessage"])
|
174
|
+
return OpenStruct.new(:number => e["ErrorNumber"], :message => e["ErrorMessage"])
|
175
|
+
end
|
176
|
+
|
177
|
+
# Or wait! Some API's have a totally different way of reporting errors, great!
|
178
|
+
if hash && (e = hash["Authentication"]) && e["Problem"]
|
179
|
+
return OpenStruct.new(:number => e["Problem"]["Code"], :message => e["Problem"]["Message"])
|
180
|
+
end
|
181
|
+
|
182
|
+
return nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'borrow_direct'
|
2
|
+
require 'borrow_direct/request'
|
3
|
+
|
4
|
+
module BorrowDirect
|
5
|
+
# The BorrowDirect RequestItem service, for placing a request
|
6
|
+
# http://borrowdirect.pbworks.com/w/file/86126056/RequestItem.docx
|
7
|
+
#
|
8
|
+
# You can also use #find_item_request to get the raw BD response as a ruby hash
|
9
|
+
class RequestItem < Request
|
10
|
+
attr_reader :patron_barcode, :patron_library_symbol
|
11
|
+
attr_reader :authorization_id
|
12
|
+
|
13
|
+
@@api_path = "/dws/item/add"
|
14
|
+
@@valid_search_types = %w{ISBN ISSN LCCN OCLC Control }
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
def initialize(patron_barcode,
|
19
|
+
patron_library_symbol = Defaults.library_symbol)
|
20
|
+
super(@@api_path)
|
21
|
+
|
22
|
+
@patron_barcode = patron_barcode
|
23
|
+
@patron_library_symbol = patron_library_symbol
|
24
|
+
|
25
|
+
# BD sometimes unpredictably returns this error when it means
|
26
|
+
# "no results", other times it doens't. We don't want to raise on it.
|
27
|
+
self.expected_error_codes << "PUBRI004"
|
28
|
+
end
|
29
|
+
|
30
|
+
# need to send a key and value for a valid exact_search type
|
31
|
+
# type can be string or symbol, lowercase or uppercase.
|
32
|
+
#
|
33
|
+
# Also a pickup_location -- can pass in nil, and we'll send no
|
34
|
+
# PickupLocation to BD, which it seems to accept, not sure what it
|
35
|
+
# does with it.
|
36
|
+
#
|
37
|
+
# Returns the actual complete BD response hash. You may want
|
38
|
+
# #make_request instead
|
39
|
+
#
|
40
|
+
# finder.request_item_request(pickup_location, :isbn => "12345545456")
|
41
|
+
# finder.request_item_request(pickup_location, :lccn => "12345545456")
|
42
|
+
# finder.request_item_request(pickup_location, :oclc => "12345545456")
|
43
|
+
def request_item_request(pickup_location, options)
|
44
|
+
search_type, search_value = nil, nil
|
45
|
+
options.each_pair do |key, value|
|
46
|
+
if @@valid_search_types.include? key.to_s.upcase
|
47
|
+
if search_type || search_value
|
48
|
+
raise ArgumentError.new("Only one search criteria at a time is allowed: '#{options}'")
|
49
|
+
end
|
50
|
+
|
51
|
+
search_type, search_value = key, value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
unless search_type && search_value
|
55
|
+
raise ArgumentError.new("Missing valid search type and value: '#{options}'")
|
56
|
+
end
|
57
|
+
|
58
|
+
request exact_search_request_hash(pickup_location, search_type, search_value)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Pass in a BD exact search and pickup location eg
|
62
|
+
# make_request(pickup_location, :isbn => isbn)
|
63
|
+
#
|
64
|
+
# Pass in nil for pickup_location if... not sure exactly what
|
65
|
+
# BD will do, but it does allow it.
|
66
|
+
#
|
67
|
+
# Returns the BD RequestNumber, or nil if a request could
|
68
|
+
# not be made
|
69
|
+
#
|
70
|
+
# See also make_request! to raise if request can not be made
|
71
|
+
def make_request(pickup_location, options)
|
72
|
+
resp = request_item_request(pickup_location, options)
|
73
|
+
|
74
|
+
return extract_request_number(resp)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Like make_request, but will raise a BorrowDirect::Error if
|
78
|
+
# item can't be requested.
|
79
|
+
def make_request!(pickup_location, options)
|
80
|
+
resp = request_item_request(pickup_location, options)
|
81
|
+
|
82
|
+
number = extract_request_number(resp)
|
83
|
+
|
84
|
+
if number.nil?
|
85
|
+
raise BorrowDirect::Error.new("Can not request for: #{options.inspect}: #{resp.inspect}")
|
86
|
+
end
|
87
|
+
|
88
|
+
return number
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def extract_request_number(resp)
|
94
|
+
return (resp["Request"] && resp["Request"]["RequestNumber"])
|
95
|
+
end
|
96
|
+
|
97
|
+
def exact_search_request_hash(pickup_location, type, value)
|
98
|
+
hash = {
|
99
|
+
"PartnershipId" => Defaults.partnership_id,
|
100
|
+
"AuthorizationId" => need_auth_id(self.patron_barcode, self.patron_library_symbol),
|
101
|
+
"PickupLocation" => pickup_location,
|
102
|
+
"ExactSearch" => [
|
103
|
+
{
|
104
|
+
"Type" => type.to_s.upcase,
|
105
|
+
"Value" => value
|
106
|
+
}
|
107
|
+
]
|
108
|
+
}
|
109
|
+
|
110
|
+
if pickup_location
|
111
|
+
hash["PickupLocation"] = pickup_location
|
112
|
+
end
|
113
|
+
|
114
|
+
return hash
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'borrow_direct'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module BorrowDirect
|
5
|
+
# Info from Relais/BD on type and status:
|
6
|
+
#
|
7
|
+
# valid 'type' values for requests:
|
8
|
+
#
|
9
|
+
# xdays: Retrieve requests submitted within the last xdays (for example, one would include two parameters: type=xdays&xdays=7 to retrieve requests submitted within the last 7 days)
|
10
|
+
# all: Retrieve all request submitted by the user
|
11
|
+
# open: Retrieve all open requests i.e. requests which haven't been fulfilled yet.
|
12
|
+
# allposttoweb: BD doesn't use the post-to-web application; you can ignore this.
|
13
|
+
# unopenedposttoweb: You can safely ignore this.
|
14
|
+
# onloan: Retrieve requests which are on loan
|
15
|
+
#
|
16
|
+
# possible RequestStatus values in results:
|
17
|
+
#
|
18
|
+
# ENTERED: Request has been entered and yet to be processed.
|
19
|
+
# IN_PROCESS: Request is being processed.
|
20
|
+
# CANCELLED: Request has been cancelled.
|
21
|
+
# UNFILLED: Request can't be filled.
|
22
|
+
# SHIPPED: Item has been shipped.
|
23
|
+
# ARTICLE_SENT: A scanned document or an electronic document has been sent (It doesn't apply to BD)
|
24
|
+
# ON_LOAN: Request is on loan.
|
25
|
+
# OVERDUE: Item is over due.
|
26
|
+
# COMPLETE: Item has been returned and request is completed.
|
27
|
+
# UNKNOWN: You should never see this; this indicates there is probably an inconsistency with the record somewhere. You probably want to flag and report this.
|
28
|
+
class RequestQuery < Request
|
29
|
+
attr_reader :patron_barcode, :patron_library_symbol
|
30
|
+
|
31
|
+
@@api_path = "/portal-service/request/query/my"
|
32
|
+
|
33
|
+
# I'm not exactly sure what these mean either.
|
34
|
+
@@query_types = %w{xdays all open allposttoweb unopenedposttoweb onloan}
|
35
|
+
|
36
|
+
|
37
|
+
def initialize(patron_barcode,
|
38
|
+
patron_library_symbol = Defaults.library_symbol)
|
39
|
+
super(@@api_path)
|
40
|
+
|
41
|
+
@patron_barcode = patron_barcode
|
42
|
+
@patron_library_symbol = patron_library_symbol
|
43
|
+
self.http_method = :get
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns raw BD response as a hash.
|
47
|
+
# * type defaults to 'all', but can be BD values of xdays, all
|
48
|
+
# open, allposttoweb, unopenedposttoweb, onloan.
|
49
|
+
# xdays not really supported yet, cause no way to pass an xdays param yet
|
50
|
+
# * full_record can be true or false, defaults to false.
|
51
|
+
def request_query_request(type = "all", full_record = false)
|
52
|
+
query_params = {
|
53
|
+
"aid" => need_auth_id(patron_barcode, patron_library_symbol),
|
54
|
+
"type" => type.to_s,
|
55
|
+
"fullRecord" => (full_record ? "1" : "0")
|
56
|
+
}
|
57
|
+
|
58
|
+
request query_params
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns an array of BorrowDirect::RequestQuery::Item
|
62
|
+
# * type defaults to 'all', but can be BD values of xdays, all
|
63
|
+
# open, allposttoweb, unopenedposttoweb, onloan.
|
64
|
+
# xdays not really supported yet, cause no way to pass an xdays param yet
|
65
|
+
# * full_record can be true or false, defaults to false.
|
66
|
+
def requests(*args)
|
67
|
+
response = request_query_request(*args)
|
68
|
+
|
69
|
+
results = []
|
70
|
+
|
71
|
+
response["QueryResult"]["MyRequestRecords"].each do |item_hash|
|
72
|
+
results << BorrowDirect::RequestQuery::Item.new(item_hash)
|
73
|
+
end
|
74
|
+
|
75
|
+
return results
|
76
|
+
end
|
77
|
+
|
78
|
+
class Item
|
79
|
+
# fullRecord == 0 values
|
80
|
+
attr_reader :request_number, :title, :date_submitted, :allow_renew,
|
81
|
+
:allow_cancel, :request_status, :request_status_date
|
82
|
+
# fullRecord == 1 values, not all are applicable for BorrowDirect,
|
83
|
+
# and many may be nil.
|
84
|
+
attr_reader :publication_type, :publication_date, :publication_place,
|
85
|
+
:volume, :issue, :edition, :issn, :issn2, :isbn, :isbn2,
|
86
|
+
:ismn, :pages_requested, :delivery_date
|
87
|
+
|
88
|
+
def initialize(hash)
|
89
|
+
# basic record values
|
90
|
+
@request_number = hash["RequestNumber"]
|
91
|
+
@title = hash["Title"]
|
92
|
+
@date_submitted = DateTime.iso8601 hash["ISO8601DateSubmitted"]
|
93
|
+
@allow_renew = hash["AllowRenew"]
|
94
|
+
@allow_cancel = hash["AllowCancel"]
|
95
|
+
@request_status = hash["RequestStatus"]
|
96
|
+
@request_status_date = DateTime.iso8601 hash["ISO8601RequestStatusDate"]
|
97
|
+
|
98
|
+
# full record values
|
99
|
+
@publicaition_type = hash["PublicationType"]
|
100
|
+
@publication_date = hash["PublicationDate"] # BD just gives us a string
|
101
|
+
@publication_place = hash["PublicationPlace"]
|
102
|
+
@volume = hash["Volume"]
|
103
|
+
@issue = hash["Issue"]
|
104
|
+
@edition = hash["Edition"]
|
105
|
+
@issn = hash["Issn"]
|
106
|
+
@issn2 = hash["Issn2"]
|
107
|
+
@isbn = hash["Isbn"]
|
108
|
+
@isbn2 = hash["Isbn2"]
|
109
|
+
@ismn = hash["Ismn"]
|
110
|
+
@pages_requested = hash["PagesRequested"]
|
111
|
+
if hash["ISO8601DeliveryDate"]
|
112
|
+
@delivery_date = DateTime.iso8601 hash["ISO8601DeliveryDate"]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module BorrowDirect
|
2
|
+
module Util
|
3
|
+
# A utility method that lets you access a nested hash,
|
4
|
+
# returning nil if any intermediate hashes are unavailable.
|
5
|
+
def hash_key_path(hash, *path)
|
6
|
+
result = nil
|
7
|
+
|
8
|
+
path.each do |key|
|
9
|
+
return nil unless hash.respond_to? :"[]"
|
10
|
+
result = hash = hash[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
return result
|
14
|
+
end
|
15
|
+
module_function :hash_key_path
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "borrow_direct/version"
|
2
|
+
|
3
|
+
require 'borrow_direct/defaults'
|
4
|
+
require 'borrow_direct/error'
|
5
|
+
require 'borrow_direct/util'
|
6
|
+
|
7
|
+
require 'borrow_direct/authentication'
|
8
|
+
|
9
|
+
require 'borrow_direct/request'
|
10
|
+
require 'borrow_direct/find_item'
|
11
|
+
require 'borrow_direct/request_item'
|
12
|
+
require 'borrow_direct/request_query'
|
13
|
+
|
14
|
+
require 'borrow_direct/generate_query'
|
15
|
+
|
16
|
+
|
17
|
+
module BorrowDirect
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'json'
|
3
|
+
require 'httpclient'
|
4
|
+
|
5
|
+
|
6
|
+
describe "Authentication", :vcr => {:tag => :bd_auth} do
|
7
|
+
describe "raw request to verify HTTP api" do
|
8
|
+
it "works" do
|
9
|
+
uri = BorrowDirect::Defaults.api_base.chomp("/") + "/portal-service/user/authentication/patron"
|
10
|
+
|
11
|
+
|
12
|
+
request_hash = {
|
13
|
+
"AuthenticationInformation" => {
|
14
|
+
"LibrarySymbol" => VCRFilter[:bd_library_symbol],
|
15
|
+
"PatronId" => VCRFilter[:bd_patron]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
http = HTTPClient.new
|
20
|
+
response = http.post uri, JSON.generate(request_hash), {"Content-Type" => "application/json", "User-Agent" => "ruby borrow_direct gem (#{BorrowDirect::VERSION}) https://github.com/jrochkind/borrow_direct", "Accept-Language" => "en"}
|
21
|
+
|
22
|
+
assert_equal 200, response.code
|
23
|
+
assert_present response.body
|
24
|
+
|
25
|
+
response_hash = JSON.parse response.body
|
26
|
+
|
27
|
+
assert_present response_hash
|
28
|
+
|
29
|
+
assert_present response_hash["Authentication"]["AuthnUserInfo"]["AId"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "Makes a request succesfully" do
|
34
|
+
bd = BorrowDirect::Authentication.new(VCRFilter[:bd_patron] , VCRFilter[:bd_library_symbol])
|
35
|
+
response = bd.authentication_request
|
36
|
+
|
37
|
+
assert_present response
|
38
|
+
assert_present response["Authentication"]["AuthnUserInfo"]["AId"]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "Raises for bad library symbol" do
|
42
|
+
bd = BorrowDirect::Authentication.new(VCRFilter[:bd_patron] , "BAD_SYMBOL")
|
43
|
+
assert_raises(BorrowDirect::Error) do
|
44
|
+
bd.authentication_request
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "Raises for bad patron barcode" do
|
49
|
+
bd = BorrowDirect::Authentication.new("BAD_BARCODE", VCRFilter[:bd_library_symbol])
|
50
|
+
assert_raises(BorrowDirect::Error) do
|
51
|
+
bd.authentication_request
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "get_auth_id" do
|
56
|
+
it "returns an auth_id for a good request" do
|
57
|
+
bd = BorrowDirect::Authentication.new(VCRFilter[:bd_patron] , VCRFilter[:bd_library_symbol])
|
58
|
+
assert_present bd.get_auth_id
|
59
|
+
end
|
60
|
+
|
61
|
+
it "raises for a bad library symbol" do
|
62
|
+
bd = BorrowDirect::Authentication.new(VCRFilter[:bd_patron] , "BAD_SYMBOL")
|
63
|
+
assert_raises(BorrowDirect::Error) do
|
64
|
+
bd.get_auth_id
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it "raises for a bad patron barcode" do
|
69
|
+
bd = BorrowDirect::Authentication.new("BAD_BARCODE", VCRFilter[:bd_library_symbol])
|
70
|
+
assert_raises(BorrowDirect::Error) do
|
71
|
+
bd.get_auth_id
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
end
|