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