koha 0.0.1
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.
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/CHANGELOG +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +21 -0
- data/koha.gemspec +25 -0
- data/lib/koha.rb +20 -0
- data/lib/koha/client.rb +210 -0
- data/lib/koha/connection.rb +70 -0
- data/lib/koha/error.rb +75 -0
- data/lib/koha/uri.rb +59 -0
- data/lib/koha/version.rb +6 -0
- data/spec/api/client_spec.rb +172 -0
- data/spec/api/connection_spec.rb +152 -0
- data/spec/api/uri_spec.rb +71 -0
- data/spec/spec_helper.rb +8 -0
- data/tasks/spec.rake +28 -0
- data/tasks/yard.rake +6 -0
- metadata +178 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 fitz
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Koha [](https://travis-ci.org/cfitz/koha) [](https://codeclimate.com/github/cfitz/koha)
|
2
|
+
|
3
|
+
A simple ruby wrapper for the Koha ILS RESTFUL API.
|
4
|
+
|
5
|
+
### Prerequisites
|
6
|
+
|
7
|
+
You must install the RESTFUL api code to your instance of Koha. This project can be found here =>
|
8
|
+
http://git.biblibre.com/?p=koha-restful;a=summary
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'koha'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install koha
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Here a quick irb walk-through...
|
27
|
+
|
28
|
+
> $ irb
|
29
|
+
|
30
|
+
> 1.9.3-p286 :001 > require 'koha'
|
31
|
+
=> true
|
32
|
+
|
33
|
+
Make a Connection...
|
34
|
+
|
35
|
+
> 1.9.3-p286 :002 > k = Koha.connect({:url => "http://my.library.se/cgi-bin/koha/rest.pl"} )
|
36
|
+
> => #<Koha::Client:0x007fbcec9b6fd0 @uri=#<URI::HTTP:0x007fbcec9c3a00 URL:http://my.library.se/cgi-bin/koha/rest.pl/>, @proxy=nil, @connection=#<Koha::Connection:0x007fbcec9b6ff8>, @options={:url=>"http://my.library.se/cgi-bin/koha/rest.pl"}>
|
37
|
+
|
38
|
+
now get branch information...
|
39
|
+
|
40
|
+
> 1.9.3-p286 :003 > k.branches
|
41
|
+
> => [ { "name"=>"World Maritime University Library", "code"=>"WMU"}, { "name"=>"ebrary", "code"=>"EBR" } ]
|
42
|
+
|
43
|
+
|
44
|
+
find a bibliographic record
|
45
|
+
> 1.9.3-p286 :006 > k.find_biblio("17454")
|
46
|
+
> => [{"withdrawn"=>"1", "biblioitemnumber"=>"17454", .......
|
47
|
+
|
48
|
+
check is an biblio is holdable.....
|
49
|
+
> 1.9.3-p286 :009 > k.biblio_holdable?("17454" )
|
50
|
+
> => false
|
51
|
+
|
52
|
+
... for a specific user ...
|
53
|
+
> 1.9.3-p286 :011 > k.biblio_holdable?("17454", :borrowernumber => "544" )
|
54
|
+
> => true
|
55
|
+
|
56
|
+
|
57
|
+
... by user name ...
|
58
|
+
> 1.9.3-p286 :012 > k.biblio_holdable?("17454", :borrowername => "cf" )
|
59
|
+
> => true
|
60
|
+
|
61
|
+
get the user's holds.
|
62
|
+
> 1.9.3-p286 :013 > k.user_holds :borrowername => "cf"
|
63
|
+
> => [{"itemnumber"=>nil, "branchname"=>"World Maritime University Library", "itemcallnumber"=>nil, "hold_id"=>nil, "reservedate"=>"2013-02-20", "barcode"=>nil, "found"=>nil, "biblionumber"=>"76356", "cancellationdate"=>nil, "title"=>"Asian approaches to international law and the legacy of colonialism and imperialism :", "rank"=>"1", "branchcode"=>"WMU"}]
|
64
|
+
|
65
|
+
or get the user's issues
|
66
|
+
|
67
|
+
> 1.9.3-p286 :014 > k.user_issues :borrowernumber => "544"
|
68
|
+
> => [{"itemnumber"=>"42414", "itemcallnumber"=>"KD1819 .H54 2003", "barcode"=>"022593", "date_due"=>"2013-03-11T23:59:00", "renewable"=>true, "issuedate"=>"2012-11-21T00:00:00", "biblionumber"=>"17454", "title"=>"Maritime law", "borrowernumber"=>"544", "branchcode"=>"WMU"}]
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
### Development
|
73
|
+
|
74
|
+
Checkout the code. rake will run the tests. rake coverage will generate coverage. rake yard will generate documentation.
|
75
|
+
|
76
|
+
|
77
|
+
## Contributing
|
78
|
+
|
79
|
+
1. Fork it
|
80
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
81
|
+
2.5 Write the code and add tests.
|
82
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
83
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
84
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
require 'rubygems/package_task'
|
5
|
+
|
6
|
+
task :environment do
|
7
|
+
require File.dirname(__FILE__) + '/lib/koha_client'
|
8
|
+
end
|
9
|
+
|
10
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
11
|
+
|
12
|
+
task :default => ['spec:api']
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
task :coverage do
|
17
|
+
# add simplecov
|
18
|
+
ENV["COVERAGE"] = 'yes'
|
19
|
+
# run the specs
|
20
|
+
Rake::Task['spec:api'].execute
|
21
|
+
end
|
data/koha.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require "koha"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "koha"
|
8
|
+
s.summary = "A Ruby client for Koha ILSDI interface"
|
9
|
+
s.description = %q{Easy interface for the Koha ILSDI API (https://github.com/Koha-Community/Koha/blob/master/C4/ILSDI/Services.pm) }
|
10
|
+
s.version = Koha.version
|
11
|
+
s.authors = ["chris fitzpatrick"]
|
12
|
+
s.email = ["chrisfitzpat@gmail.com"]
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency 'simplecov', '0.7.1'
|
19
|
+
s.add_development_dependency 'yard', '~> 0.8.4.1'
|
20
|
+
s.add_development_dependency 'webmock', '~> 1.9.3'
|
21
|
+
s.add_development_dependency 'rake', '~> 10.0.3'
|
22
|
+
s.add_development_dependency 'rdoc', '~> 3.9.5'
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.6.0'
|
24
|
+
s.add_development_dependency 'redcarpet'
|
25
|
+
end
|
data/lib/koha.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$: << "#{File.dirname(__FILE__)}" unless $:.include? File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
module Koha
|
6
|
+
|
7
|
+
%W(Client Error Connection Uri Version).each{|n| autoload n.to_sym, "koha/#{n.downcase}"}
|
8
|
+
|
9
|
+
def self.version; "0.0.1" end
|
10
|
+
|
11
|
+
VERSION = self.version
|
12
|
+
|
13
|
+
def self.connect *args
|
14
|
+
driver = Class === args[0] ? args[0] : Koha::Connection
|
15
|
+
opts = Hash === args[-1] ? args[-1] : {}
|
16
|
+
Client.new driver.new, opts
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
data/lib/koha/client.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Koha::Client
|
4
|
+
|
5
|
+
attr_reader :connection, :uri, :proxy, :options
|
6
|
+
|
7
|
+
def initialize connection, options = {}
|
8
|
+
@proxy = @uri = nil
|
9
|
+
@connection = connection
|
10
|
+
unless false === options[:url]
|
11
|
+
url = options[:url] ? options[:url].dup : 'http://localhost/cgi-bin/koha/rest.pl/'
|
12
|
+
url << "/" unless url[-1] == ?/
|
13
|
+
@uri = Koha::Uri.create url
|
14
|
+
if options[:proxy]
|
15
|
+
proxy_url = options[:proxy].dup
|
16
|
+
proxy_url << "/" unless proxy_url.nil? or proxy_url[-1] == ?/
|
17
|
+
@proxy = Koha::Uri.create proxy_url if proxy_url
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns the uri proxy if present,
|
24
|
+
# otherwise just the uri object.
|
25
|
+
def base_uri
|
26
|
+
@proxy ? @proxy : @uri
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create the get, post, and head methods
|
30
|
+
%W(get post put).each do |meth|
|
31
|
+
class_eval <<-RUBY
|
32
|
+
def #{meth} path, opts = {}, &block
|
33
|
+
send_and_receive path, opts.merge(:method => :#{meth}), &block
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# +send_and_receive+ is the main request method responsible for sending requests to the +connection+ object.
|
40
|
+
#
|
41
|
+
# "path" : A string value that directs the client to the API REST method
|
42
|
+
# "opts" : A hash, which can contain the following keys:
|
43
|
+
# :method : optional - the http method (:get, :post or :put)
|
44
|
+
# :params : optional - the query string params in hash form
|
45
|
+
# All other options are passed right along to the connection's +send_and_receive+ method (:get, :post, or :put)
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# creates a request context hash,
|
49
|
+
# sends it to the connection's +execute+ method
|
50
|
+
# which returns a simple hash,
|
51
|
+
# then passes the request/response into +adapt_response+.
|
52
|
+
def send_and_receive path, opts
|
53
|
+
request_context = build_request path, opts
|
54
|
+
[:open_timeout, :read_timeout].each do |k|
|
55
|
+
request_context[k] = @options[k]
|
56
|
+
end
|
57
|
+
execute request_context
|
58
|
+
end
|
59
|
+
|
60
|
+
# send a request to the connection to be submitted
|
61
|
+
def execute request_context
|
62
|
+
raw_response = connection.execute self, request_context
|
63
|
+
adapt_response(request_context, raw_response) unless raw_response.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
# +build_request+ accepts a path and options hash,
|
67
|
+
# then prepares a normalized hash to return for sending
|
68
|
+
# +build_request+ sets up the uri/query string
|
69
|
+
# and converts the +data+ arg to form-urlencoded,
|
70
|
+
# returns a hash with the following keys:
|
71
|
+
# :method
|
72
|
+
# :params
|
73
|
+
# :uri
|
74
|
+
# :path
|
75
|
+
# :query
|
76
|
+
|
77
|
+
def build_request path, opts
|
78
|
+
raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
|
79
|
+
path = path.to_s
|
80
|
+
opts[:proxy] = proxy unless proxy.nil?
|
81
|
+
opts[:method] ||= :get
|
82
|
+
opts[:params] ||= {}
|
83
|
+
opts[:params][:borrowernumber] = opts[:borrowernumber] if opts[:borrowernumber]
|
84
|
+
opts[:params][:user_name] = opts[:borrowername] if opts[:borrowername]
|
85
|
+
query = Koha::Uri.to_params(opts[:params]) unless opts[:params].empty?
|
86
|
+
opts[:query] = query
|
87
|
+
opts[:path] = path
|
88
|
+
if base_uri
|
89
|
+
opts[:uri] = URI.join(base_uri, path.to_s)
|
90
|
+
opts[:uri].merge!("?#{query}" ) if query
|
91
|
+
end
|
92
|
+
opts
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# A mixin for used by #adapt_response
|
97
|
+
module Context
|
98
|
+
attr_accessor :request, :response
|
99
|
+
end
|
100
|
+
|
101
|
+
# This method will evaluate the :body value
|
102
|
+
# if the request has an :evaulte value, the response is send to
|
103
|
+
# the evaluate_json_response, which returns just the node.
|
104
|
+
# this is a convience to simply return "false" or "true" and not a bunch of stupid
|
105
|
+
# json that has nothing that makes any god-damn sense.
|
106
|
+
# ... otherwise, the json is simply returned.
|
107
|
+
def adapt_response request, response
|
108
|
+
raise "The response does not have the correct keys => :body, :headers, :status" unless
|
109
|
+
%W(body headers status) == response.keys.map{|k|k.to_s}.sort
|
110
|
+
raise Koha::Error::Http.new request, response unless [200,302].include? response[:status]
|
111
|
+
result = request[:evaluate] ? evaluate_json_response(request, response, request[:evaluate]) : response[:body]
|
112
|
+
result.extend Context
|
113
|
+
result.request, result.response = request, response
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
##### KOHA REST API METHODS #######
|
120
|
+
|
121
|
+
### Info Methods ###
|
122
|
+
|
123
|
+
# returns a hash of [ { :code => "1", :name => "Our Branch Name "}]
|
124
|
+
def branches opts= {}
|
125
|
+
JSON.parse(get "branches", opts)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
### USER Methods ###
|
130
|
+
|
131
|
+
# returns a hash of all the users
|
132
|
+
def all_users opts= {}
|
133
|
+
JSON.parse(get "user/all", opts )
|
134
|
+
end
|
135
|
+
|
136
|
+
# returns a hash of patrons enrolled today
|
137
|
+
def today_users opts= {}
|
138
|
+
JSON.parse(get "user/today", opts)
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def user_holds opts= {}
|
143
|
+
path, opts = build_user_path("holds", opts)
|
144
|
+
JSON.parse(get path, opts)
|
145
|
+
end
|
146
|
+
|
147
|
+
def user_issues opts= {}
|
148
|
+
path, opts = build_user_path("issues", opts)
|
149
|
+
JSON.parse(get path, opts)
|
150
|
+
end
|
151
|
+
|
152
|
+
def build_user_path rest_method, opts= {}
|
153
|
+
raise ArgumentError unless ( opts[:borrowernumber] or opts[:borrowername] ) #we have to be passed either a name or number
|
154
|
+
borrowernumber, borrowername = opts.delete(:borrowernumber), opts.delete(:borrowername)
|
155
|
+
path = borrowernumber ? "user/byid/#{borrowernumber}/#{rest_method}" : "user/#{borrowername}/#{rest_method}"
|
156
|
+
return path, opts
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
### Biblio and Item Methods ###
|
161
|
+
|
162
|
+
# This method will get the item record from koha given the biblionumber
|
163
|
+
def find_biblio biblionumber, opts = {}
|
164
|
+
biblionumber = biblionumber.to_s
|
165
|
+
JSON.parse(get "biblio/#{biblionumber}/items", opts)
|
166
|
+
end
|
167
|
+
|
168
|
+
# wrapper to check if a biblio is holdable
|
169
|
+
# take a koha biblio number and standard client opts
|
170
|
+
def biblio_holdable?(biblionumber, opts = {})
|
171
|
+
is_holdable?(:biblio, biblionumber, opts )
|
172
|
+
end
|
173
|
+
|
174
|
+
# wrapper to check a biblio items holdabale statues.
|
175
|
+
# takes a koha bilionumber and standard client opts
|
176
|
+
# this returns just a hash of the items and their status, no need to evaulate.
|
177
|
+
def biblio_items_holdable?(biblionumber, opts = {} )
|
178
|
+
opts ||= {}
|
179
|
+
opts[:holdable] = "items_holdable_status"
|
180
|
+
opts[:evaluate] ||= false
|
181
|
+
is_holdable?(:biblio, biblionumber, opts)
|
182
|
+
end
|
183
|
+
|
184
|
+
# wrapper to check is an item is holdable
|
185
|
+
def item_holdable?(itemnumber, opts = {})
|
186
|
+
is_holdable?(:item, itemnumber, opts)
|
187
|
+
end
|
188
|
+
|
189
|
+
def is_holdable?(koha_type, identifier, opts = {} )
|
190
|
+
opts ||= {}
|
191
|
+
opts[:evaluate] = :is_holdable unless opts[:evaluate] == false
|
192
|
+
holdable = opts[:holdable] ? opts[:holdable] : "holdable"
|
193
|
+
koha_type = koha_type.to_s == "item" ? "item" : "biblio"
|
194
|
+
identifier = identifier.to_s
|
195
|
+
get "#{koha_type}/#{identifier}/#{holdable}", opts
|
196
|
+
end
|
197
|
+
|
198
|
+
protected
|
199
|
+
|
200
|
+
# this is used to retrun a ruby primitive based on a json node. For example holdable returns { "is_holdable" : true, "reasons" : [] }
|
201
|
+
# and we just want true.
|
202
|
+
def evaluate_json_response request, response, node
|
203
|
+
json = JSON.parse(response[:body])
|
204
|
+
json[node.to_s]
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
|
5
|
+
# taken from RSOLR's http client
|
6
|
+
class Koha::Connection
|
7
|
+
|
8
|
+
def execute client, request_context
|
9
|
+
h = http request_context[:uri], request_context[:proxy], request_context[:read_timeout], request_context[:open_timeout]
|
10
|
+
request = setup_raw_request request_context
|
11
|
+
request.body = request_context[:data] if request_context[:method] == :post and request_context[:data]
|
12
|
+
begin
|
13
|
+
response = h.request request
|
14
|
+
charset = response.type_params["charset"]
|
15
|
+
{:status => response.code.to_i, :headers => response.to_hash, :body => force_charset(response.body, charset)}
|
16
|
+
rescue Errno::ECONNREFUSED => e
|
17
|
+
raise(Errno::ECONNREFUSED.new(request_context.inspect))
|
18
|
+
# catch the undefined closed? exception -- this is a confirmed ruby bug
|
19
|
+
rescue NoMethodError
|
20
|
+
$!.message == "undefined method `closed?' for nil:NilClass" ?
|
21
|
+
raise(Errno::ECONNREFUSED.new) :
|
22
|
+
raise($!)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# This returns a singleton of a Net::HTTP or Net::HTTP.Proxy request object.
|
29
|
+
def http uri, proxy = nil, read_timeout = nil, open_timeout = nil
|
30
|
+
@http ||= (
|
31
|
+
http = if proxy
|
32
|
+
proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
|
33
|
+
Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port
|
34
|
+
else
|
35
|
+
Net::HTTP.new uri.host, uri.port
|
36
|
+
end
|
37
|
+
http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
38
|
+
http.read_timeout = read_timeout if read_timeout
|
39
|
+
http.open_timeout = open_timeout if open_timeout
|
40
|
+
http
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
def setup_raw_request request_context
|
46
|
+
http_method = case request_context[:method]
|
47
|
+
when :get
|
48
|
+
Net::HTTP::Get
|
49
|
+
when :post
|
50
|
+
Net::HTTP::Post
|
51
|
+
when :put
|
52
|
+
Net::HTTP::Put
|
53
|
+
else
|
54
|
+
raise "Only :get, :post and :head http method types are allowed."
|
55
|
+
end
|
56
|
+
headers = request_context[:headers] || {}
|
57
|
+
raw_request = http_method.new request_context[:uri].request_uri
|
58
|
+
raw_request.initialize_http_header headers
|
59
|
+
raw_request.basic_auth(request_context[:uri].user, request_context[:uri].password) if request_context[:uri].user && request_context[:uri].password
|
60
|
+
raw_request
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def force_charset body, charset
|
66
|
+
return body unless charset and body.respond_to?(:force_encoding)
|
67
|
+
body.force_encoding(charset)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/koha/error.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Koha::Error
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
class Http < RuntimeError
|
6
|
+
|
7
|
+
|
8
|
+
# ripped right from ActionPack
|
9
|
+
# Defines the standard HTTP status codes, by integer, with their
|
10
|
+
# corresponding default message texts.
|
11
|
+
# Source: http://www.iana.org/assignments/http-status-codes
|
12
|
+
STATUS_CODES = {
|
13
|
+
100 => "Continue",
|
14
|
+
101 => "Switching Protocols",
|
15
|
+
102 => "Processing",
|
16
|
+
|
17
|
+
200 => "OK",
|
18
|
+
201 => "Created",
|
19
|
+
202 => "Accepted",
|
20
|
+
203 => "Non-Authoritative Information",
|
21
|
+
204 => "No Content",
|
22
|
+
205 => "Reset Content",
|
23
|
+
206 => "Partial Content",
|
24
|
+
207 => "Multi-Status",
|
25
|
+
226 => "IM Used",
|
26
|
+
|
27
|
+
300 => "Multiple Choices",
|
28
|
+
301 => "Moved Permanently",
|
29
|
+
302 => "Found",
|
30
|
+
303 => "See Other",
|
31
|
+
304 => "Not Modified",
|
32
|
+
305 => "Use Proxy",
|
33
|
+
307 => "Temporary Redirect",
|
34
|
+
|
35
|
+
400 => "Bad Request",
|
36
|
+
401 => "Unauthorized",
|
37
|
+
402 => "Payment Required",
|
38
|
+
403 => "Forbidden",
|
39
|
+
404 => "Not Found",
|
40
|
+
405 => "Method Not Allowed",
|
41
|
+
406 => "Not Acceptable",
|
42
|
+
407 => "Proxy Authentication Required",
|
43
|
+
408 => "Request Timeout",
|
44
|
+
409 => "Conflict",
|
45
|
+
410 => "Gone",
|
46
|
+
411 => "Length Required",
|
47
|
+
412 => "Precondition Failed",
|
48
|
+
413 => "Request Entity Too Large",
|
49
|
+
414 => "Request-URI Too Long",
|
50
|
+
415 => "Unsupported Media Type",
|
51
|
+
416 => "Requested Range Not Satisfiable",
|
52
|
+
417 => "Expectation Failed",
|
53
|
+
422 => "Unprocessable Entity",
|
54
|
+
423 => "Locked",
|
55
|
+
424 => "Failed Dependency",
|
56
|
+
426 => "Upgrade Required",
|
57
|
+
|
58
|
+
500 => "Internal Server Error",
|
59
|
+
501 => "Not Implemented",
|
60
|
+
502 => "Bad Gateway",
|
61
|
+
503 => "Service Unavailable",
|
62
|
+
504 => "Gateway Timeout",
|
63
|
+
505 => "HTTP Version Not Supported",
|
64
|
+
507 => "Insufficient Storage",
|
65
|
+
510 => "Not Extended"
|
66
|
+
}
|
67
|
+
|
68
|
+
def initialize request, response
|
69
|
+
@request, @response = request, response
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
data/lib/koha/uri.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Koha::Uri
|
4
|
+
|
5
|
+
def create url
|
6
|
+
::URI.parse url[-1] == ?/ ? url : "#{url}/"
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns a query string param pair as a string.
|
10
|
+
# Both key and value are escaped.
|
11
|
+
def build_param(k,v, escape = true)
|
12
|
+
escape ?
|
13
|
+
"#{escape_query_value(k)}=#{escape_query_value(v)}" :
|
14
|
+
"#{k}=#{v}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
18
|
+
# String#bytesize under 1.9.
|
19
|
+
if ''.respond_to?(:bytesize)
|
20
|
+
def bytesize(string)
|
21
|
+
string.bytesize
|
22
|
+
end
|
23
|
+
else
|
24
|
+
def bytesize(string)
|
25
|
+
string.size
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a ILSDI based query string.
|
30
|
+
# Keys that have arrays values are set multiple times:
|
31
|
+
# params_to_solr(:service => 'foo', :biblionumbers => ['1', '2'])
|
32
|
+
# is converted to:
|
33
|
+
# ?service=foo&biblinumbers=1+2
|
34
|
+
def to_params(params, escape = true)
|
35
|
+
mapped = params.map do |k, v|
|
36
|
+
next if v.to_s.empty?
|
37
|
+
if v.class == Array
|
38
|
+
build_param k, v.join("+"), false
|
39
|
+
else
|
40
|
+
build_param k, v, escape
|
41
|
+
end
|
42
|
+
end
|
43
|
+
mapped.compact.join("&")
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Performs URI escaping so that you can construct proper
|
48
|
+
# query strings faster. Use this rather than the cgi.rb
|
49
|
+
# version since it's faster.
|
50
|
+
# (Stolen from Rack).
|
51
|
+
def escape_query_value(s)
|
52
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/u) {
|
53
|
+
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
54
|
+
}.tr(' ', '+')
|
55
|
+
end
|
56
|
+
|
57
|
+
extend self
|
58
|
+
|
59
|
+
end
|
data/lib/koha/version.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe "Koha::Client" do
|
3
|
+
|
4
|
+
module ClientHelper
|
5
|
+
def client
|
6
|
+
@client ||= (
|
7
|
+
connection = Koha::Connection.new
|
8
|
+
Koha::Client.new connection, :url => "http://localhost/koha", :read_timeout => 42, :open_timeout=>43
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "initialize" do
|
14
|
+
it "should accept whatevs and set it as the @connection" do
|
15
|
+
Koha::Client.new(:whatevs).connection.should == :whatevs
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context "send_and_receive" do
|
22
|
+
include ClientHelper
|
23
|
+
it "should forward these method calls the #connection object" do
|
24
|
+
[:get, :post, :put].each do |meth|
|
25
|
+
client.connection.should_receive(:execute).
|
26
|
+
and_return({:status => 200, :body => "{}", :headers => {}})
|
27
|
+
client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should be timeout aware" do
|
32
|
+
[:get, :post, :put].each do |meth|
|
33
|
+
client.connection.should_receive(:execute).with(client, hash_including(:read_timeout => 42, :open_timeout=>43))
|
34
|
+
client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "post" do
|
40
|
+
include ClientHelper
|
41
|
+
it "should pass the expected params to the connection's #execute method" do
|
42
|
+
request_opts = {:data => "the data", :method=>:post, :headers => {"Content-Type" => "text/plain"}}
|
43
|
+
client.connection.should_receive(:execute).
|
44
|
+
with(client, hash_including(request_opts)).
|
45
|
+
and_return(
|
46
|
+
:body => "",
|
47
|
+
:status => 200,
|
48
|
+
:headers => {"Content-Type"=>"text/plain"}
|
49
|
+
)
|
50
|
+
client.post "biblio/update", request_opts
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
context "adapt_response" do
|
57
|
+
include ClientHelper
|
58
|
+
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
context "build_request" do
|
63
|
+
include ClientHelper
|
64
|
+
it 'should return a request context array' do
|
65
|
+
result = client.build_request('select',
|
66
|
+
:method => :get,
|
67
|
+
:params => {},
|
68
|
+
:borrowernumber => "512",
|
69
|
+
:borrowername => "cf"
|
70
|
+
)
|
71
|
+
[/user_name=cf/, /borrowernumber=512/].each do |pattern|
|
72
|
+
result[:query].should match pattern
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
# Check responses to see how the KOHA RESTFUL API v1.0 responds.
|
79
|
+
context "koha methods" do
|
80
|
+
include ClientHelper
|
81
|
+
|
82
|
+
# branches
|
83
|
+
it "should call the REST API branch method with #client" do
|
84
|
+
stub_request(:get, "http://localhost/koha/branches").to_return(:status => 200, :body =>
|
85
|
+
"[{\"name\":\"World Maritime University Library\",\"code\":\"WMU\"},{\"name\":\"ebrary\",\"code\":\"EBR\"}]",
|
86
|
+
:headers => {})
|
87
|
+
client.branches
|
88
|
+
WebMock.should have_requested(:get, "http://localhost/koha/branches")
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# user
|
93
|
+
it "should call the user/all method for #all_users" do
|
94
|
+
stub_request(:get, "http://localhost/koha/user/all").to_return(:status => 200, :body =>
|
95
|
+
"[{\"categorycode\":\"T\",\"B_address\":\"\",\"contactnote\":\"\",\"ethnicity\":null,\"email\":\"foo@wmu.se\",\"password\":\"1+1898juif\",\"B_country\":\"\",\"borrowernumber\":\"5\",\"lost\":\"0\",\"branchcode\":\"WMU\",\"streettype\":null,\"altcontactaddress3\":\"\",\"contactfirstname\":null,\"title\":\"\",\"attributes\":[],\"ethnotes\":null,\"relationship\":null,\"mobile\":\"\",\"fax\":\"\",\"altcontactphone\":\"\",\"contactname\":\"WMU\",\"country\":\"Sweden\",\"dateenrolled\":\"2010-02-03\",\"altcontactstate\":null,\"guarantorid\":\"0\",\"address2\":\"\",\"borrowernotes\":\"\",\"dateexpiry\":\"2018-05-03\",\"sort2\":\"\",\"contacttitle\":null,\"phonepro\":\"+46-40-35 63 90\",\"smsalertnumber\":null,\"B_streetnumber\":null,\"emailpro\":\"mbf@wmu.se\",\"firstname\":\"Michael\",\"altcontactcountry\":\"\",\"gonenoaddress\":\"0\",\"othernames\":\"\",\"state\":null,\"dateofbirth\":null,\"altcontactaddress2\":\"\",\"B_streettype\":null,\"debarred\":null,\"B_state\":null,\"address\":\"PO Box 500\",\"B_address2\":\"\",\"privacy\":\"1\",\"streetnumber\":\"\",\"surname\":\"BALDAUF\",\"cardnumber\":\"MBF\",\"altcontactsurname\":\"\",\"altcontactzipcode\":\"\",\"opacnote\":\"\",\"altcontactfirstname\":\"\",\"userid\":\"mbf\",\"B_zipcode\":\"\",\"B_email\":\"\",\"city\":\"Malm\",\"B_phone\":\"\",\"debarredcomment\":null,\"initials\":\"MB\",\"sort1\":\"\",\"flags\":null,\"zipcode\":\"20124\",\"phone\":\"\",\"sex\":\"M\",\"altcontactaddress1\":\"\",\"B_city\":\"\"}]"
|
96
|
+
)
|
97
|
+
client.all_users
|
98
|
+
WebMock.should have_requested(:get, "http://localhost/koha/user/all")
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
it "should call the user/all method for #all_users" do
|
103
|
+
stub_request(:get, "http://localhost/koha/user/today").to_return(:status => 200, :body =>
|
104
|
+
"[{\"categorycode\":\"T\",\"B_address\":\"\",\"contactnote\":\"\",\"ethnicity\":null,\"email\":\"foo@wmu.se\",\"password\":\"1+1898juif\",\"B_country\":\"\",\"borrowernumber\":\"5\",\"lost\":\"0\",\"branchcode\":\"WMU\",\"streettype\":null,\"altcontactaddress3\":\"\",\"contactfirstname\":null,\"title\":\"\",\"attributes\":[],\"ethnotes\":null,\"relationship\":null,\"mobile\":\"\",\"fax\":\"\",\"altcontactphone\":\"\",\"contactname\":\"WMU\",\"country\":\"Sweden\",\"dateenrolled\":\"2010-02-03\",\"altcontactstate\":null,\"guarantorid\":\"0\",\"address2\":\"\",\"borrowernotes\":\"\",\"dateexpiry\":\"2018-05-03\",\"sort2\":\"\",\"contacttitle\":null,\"phonepro\":\"+46-40-35 63 90\",\"smsalertnumber\":null,\"B_streetnumber\":null,\"emailpro\":\"mbf@wmu.se\",\"firstname\":\"Michael\",\"altcontactcountry\":\"\",\"gonenoaddress\":\"0\",\"othernames\":\"\",\"state\":null,\"dateofbirth\":null,\"altcontactaddress2\":\"\",\"B_streettype\":null,\"debarred\":null,\"B_state\":null,\"address\":\"PO Box 500\",\"B_address2\":\"\",\"privacy\":\"1\",\"streetnumber\":\"\",\"surname\":\"BALDAUF\",\"cardnumber\":\"MBF\",\"altcontactsurname\":\"\",\"altcontactzipcode\":\"\",\"opacnote\":\"\",\"altcontactfirstname\":\"\",\"userid\":\"mbf\",\"B_zipcode\":\"\",\"B_email\":\"\",\"city\":\"Malm\",\"B_phone\":\"\",\"debarredcomment\":null,\"initials\":\"MB\",\"sort1\":\"\",\"flags\":null,\"zipcode\":\"20124\",\"phone\":\"\",\"sex\":\"M\",\"altcontactaddress1\":\"\",\"B_city\":\"\"}]"
|
105
|
+
)
|
106
|
+
client.today_users
|
107
|
+
WebMock.should have_requested(:get, "http://localhost/koha/user/today")
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should call the user holds method for #user_holds" do
|
111
|
+
stub_request(:get, "http://localhost/koha/user/byid/1/holds").to_return(:status => 200, :body =>
|
112
|
+
"[{\"itemnumber\":null,\"branchname\":\"World Maritime University Library\",\"itemcallnumber\":null,\"hold_id\":null,\"reservedate\":\"2013-02-20\",\"barcode\":null,\"found\":null,\"biblionumber\":\"76356\",\"cancellationdate\":null,\"title\":\"Asian approaches to international law and the legacy of colonialism and imperialism :\",\"rank\":\"1\",\"branchcode\":\"WMU\"}]" )
|
113
|
+
client.user_holds(:borrowernumber => "1")
|
114
|
+
WebMock.should have_requested(:get, "http://localhost/koha/user/byid/1/holds")
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
it "should call the user issues method for #user_issues" do
|
119
|
+
stub_request(:get, "http://localhost/koha/user/cf/issues").to_return(:status => 200, :body =>
|
120
|
+
"[{\"itemnumber\":\"42414\",\"itemcallnumber\":\"KD1819 .H54 2003\",\"barcode\":\"022593\",\"date_due\":\"2013-03-11T23:59:00\",\"renewable\":true,\"issuedate\":\"2012-11-21T00:00:00\",\"biblionumber\":\"17454\",\"title\":\"Maritime law\",\"borrowernumber\":\"544\",\"branchcode\":\"WMU\"}]" )
|
121
|
+
client.user_issues(:borrowername => "cf")
|
122
|
+
WebMock.should have_requested(:get, "http://localhost/koha/user/cf/issues")
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# biblio and items
|
127
|
+
|
128
|
+
it "should call the biblio items method for #find_biblio" do
|
129
|
+
stub_request(:get, "http://localhost/koha/biblio/1/items").to_return(:status => 200, :body =>
|
130
|
+
"[{\"withdrawn\":\"0\",\"biblioitemnumber\":\"1\",\"restricted\":null,\"wthdrawn\":\"0\",\"holdingbranchname\":\"World Maritime University Library\",\"notforloan\":\"0\",\"replacementpricedate\":\"2010-02-05\",\"itemnumber\":\"1\",\"ccode\":null,\"itemnotes\":null,\"location\":\"GEN\",\"itemcallnumber\":\"HD30.3 .A32 1989\",\"stack\":null,\"date_due\":\"\",\"barcode\":\"011376\",\"itemlost\":\"0\",\"uri\":null,\"materials\":null,\"datelastseen\":\"2010-02-05\",\"price\":null,\"issues\":null,\"homebranch\":\"WMU\",\"replacementprice\":null,\"more_subfields_xml\":null,\"cn_source\":\"lcc\",\"homebranchname\":\"World Maritime University Library\",\"booksellerid\":null,\"biblionumber\":\"1\",\"renewals\":null,\"holdingbranch\":\"WMU\",\"timestamp\":\"2012-11-28 07:47:23\",\"damaged\":\"0\",\"stocknumber\":null,\"cn_sort\":\"HD_00030_3_A32_1989\",\"reserves\":null,\"dateaccessioned\":\"2010-02-05\",\"datelastborrowed\":null,\"enumchron\":null,\"copynumber\":\"1\",\"permanent_location\":null,\"onloan\":null,\"paidfor\":null,\"itype\":\"BOOK\"}]" )
|
131
|
+
client.find_biblio("1")
|
132
|
+
WebMock.should have_requested(:get, "http://localhost/koha/biblio/1/items")
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
it "should call the biblio items method for #find_biblio" do
|
137
|
+
stub_request(:get, "http://localhost/koha/biblio/1/items").to_return(:status => 200, :body =>
|
138
|
+
"[{\"withdrawn\":\"0\",\"biblioitemnumber\":\"1\",\"restricted\":null,\"wthdrawn\":\"0\",\"holdingbranchname\":\"World Maritime University Library\",\"notforloan\":\"0\",\"replacementpricedate\":\"2010-02-05\",\"itemnumber\":\"1\",\"ccode\":null,\"itemnotes\":null,\"location\":\"GEN\",\"itemcallnumber\":\"HD30.3 .A32 1989\",\"stack\":null,\"date_due\":\"\",\"barcode\":\"011376\",\"itemlost\":\"0\",\"uri\":null,\"materials\":null,\"datelastseen\":\"2010-02-05\",\"price\":null,\"issues\":null,\"homebranch\":\"WMU\",\"replacementprice\":null,\"more_subfields_xml\":null,\"cn_source\":\"lcc\",\"homebranchname\":\"World Maritime University Library\",\"booksellerid\":null,\"biblionumber\":\"1\",\"renewals\":null,\"holdingbranch\":\"WMU\",\"timestamp\":\"2012-11-28 07:47:23\",\"damaged\":\"0\",\"stocknumber\":null,\"cn_sort\":\"HD_00030_3_A32_1989\",\"reserves\":null,\"dateaccessioned\":\"2010-02-05\",\"datelastborrowed\":null,\"enumchron\":null,\"copynumber\":\"1\",\"permanent_location\":null,\"onloan\":null,\"paidfor\":null,\"itype\":\"BOOK\"}]" )
|
139
|
+
client.find_biblio("1")
|
140
|
+
WebMock.should have_requested(:get, "http://localhost/koha/biblio/1/items")
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should call the biblio holdable for #biblio_holdable?" do
|
144
|
+
stub_request(:get, "http://localhost/koha/biblio/1/holdable").to_return(:status => 200, :body =>
|
145
|
+
"{\"is_holdable\":true,\"reasons\":[]}" )
|
146
|
+
client.biblio_holdable?("1").should be_true
|
147
|
+
WebMock.should have_requested(:get, "http://localhost/koha/biblio/1/holdable")
|
148
|
+
end
|
149
|
+
|
150
|
+
# show how to use a borrowername
|
151
|
+
it "should call the biblio holdable items for #biblio_items_holdable?" do
|
152
|
+
stub_request(:get, "http://localhost/koha/biblio/1/items_holdable_status?user_name=cf").to_return(:status => 200, :body =>
|
153
|
+
"{\"is_holdable\":true,\"reasons\":[]}" )
|
154
|
+
client.biblio_items_holdable?("1", :borrowername => "cf" ).should be_true
|
155
|
+
WebMock.should have_requested(:get, "http://localhost/koha/biblio/1/items_holdable_status?user_name=cf")
|
156
|
+
end
|
157
|
+
|
158
|
+
# show how to use a borrowernumber
|
159
|
+
it "should call the items holdable API method for #item_holdable?" do
|
160
|
+
stub_request(:get, "http://localhost/koha/item/74297/holdable?user_name=cf").to_return(:status => 200, :body =>
|
161
|
+
"{\"is_holdable\":false,\"reasons\":[]}" )
|
162
|
+
client.item_holdable?("74297", :borrowername => "cf" ).should be_false
|
163
|
+
WebMock.should have_requested(:get, "http://localhost/koha/item/74297/holdable?user_name=cf")
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
describe "Koha::Connection" do
|
5
|
+
|
6
|
+
context "setup_raw_request" do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@c = Koha::Connection.new
|
10
|
+
@base_url = "http://localhost:8983/koha/rest.pl"
|
11
|
+
@client = Koha::Client.new @c, :url => @base_url
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should set up a get request" do
|
15
|
+
req = @c.send :setup_raw_request, {:headers => {"content-type" => "application/json"}, :method => :get, :uri => URI.parse(@base_url + "/biblio/1/items?borrowername=cf")}
|
16
|
+
headers = {}
|
17
|
+
req.each_header{|k,v| headers[k] = v}
|
18
|
+
req.method.should == "GET"
|
19
|
+
headers.should == {"content-type"=>"application/json"}
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set up a post request" do
|
23
|
+
req = @c.send :setup_raw_request, {:headers => {"content-type" => "application/json"}, :method => :post, :uri => URI.parse(@base_url + "/biblio/1/items?borrowername=cf")}
|
24
|
+
headers = {}
|
25
|
+
req.each_header{|k,v| headers[k] = v}
|
26
|
+
req.method.should == "POST"
|
27
|
+
headers.should == {"content-type"=>"application/json"}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should set up a post request" do
|
31
|
+
req = @c.send :setup_raw_request, {:headers => {"content-type" => "application/json"}, :method => :put, :uri => URI.parse(@base_url + "/biblio/1/items?borrowername=cf")}
|
32
|
+
headers = {}
|
33
|
+
req.each_header{|k,v| headers[k] = v}
|
34
|
+
req.method.should == "PUT"
|
35
|
+
headers.should == {"content-type"=>"application/json"}
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should raise error if something weird is set as method" do
|
39
|
+
expect { @c.send :setup_raw_request, {:headers => {"content-type" => "application/json"}, :method => :head, :uri => URI.parse(@base_url + "/biblio/1/items?borrowername=cf")} }.to raise_error("Only :get, :post and :head http method types are allowed.")
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
context "read timeout configuration" do
|
46
|
+
let(:client) { mock.as_null_object }
|
47
|
+
|
48
|
+
let(:http) { mock(Net::HTTP).as_null_object }
|
49
|
+
|
50
|
+
subject { Koha::Connection.new }
|
51
|
+
|
52
|
+
before do
|
53
|
+
Net::HTTP.stub(:new) { http }
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should configure Net:HTTP read_timeout" do
|
57
|
+
http.should_receive(:read_timeout=).with(42)
|
58
|
+
subject.execute client, {:uri => URI.parse("http://localhost/some_uri"), :method => :get, :read_timeout => 42}
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should use Net:HTTP default read_timeout if not specified" do
|
62
|
+
http.should_not_receive(:read_timeout=)
|
63
|
+
subject.execute client, {:uri => URI.parse("http://localhost/some_uri"), :method => :get}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "open timeout configuration" do
|
68
|
+
let(:client) { mock.as_null_object }
|
69
|
+
|
70
|
+
let(:http) { mock(Net::HTTP).as_null_object }
|
71
|
+
|
72
|
+
subject { Koha::Connection.new }
|
73
|
+
|
74
|
+
before do
|
75
|
+
Net::HTTP.stub(:new) { http }
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should configure Net:HTTP open_timeout" do
|
79
|
+
http.should_receive(:open_timeout=).with(42)
|
80
|
+
subject.execute client, {:uri => URI.parse("http://localhost/some_uri"), :method => :get, :open_timeout => 42}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should use Net:HTTP default open_timeout if not specified" do
|
84
|
+
http.should_not_receive(:open_timeout=)
|
85
|
+
subject.execute client, {:uri => URI.parse("http://localhost/some_uri"), :method => :get}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "connection refused" do
|
90
|
+
let(:client) { mock.as_null_object }
|
91
|
+
|
92
|
+
let(:http) { mock(Net::HTTP).as_null_object }
|
93
|
+
let(:request_context) {
|
94
|
+
{:uri => URI.parse("http://localhost/some_uri"), :method => :get, :open_timeout => 42}
|
95
|
+
}
|
96
|
+
subject { Koha::Connection.new }
|
97
|
+
|
98
|
+
before do
|
99
|
+
Net::HTTP.stub(:new) { http }
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should configure Net:HTTP open_timeout" do
|
103
|
+
http.should_receive(:request).and_raise(Errno::ECONNREFUSED)
|
104
|
+
lambda {
|
105
|
+
subject.execute client, request_context
|
106
|
+
}.should raise_error(Errno::ECONNREFUSED, /#{request_context}/)
|
107
|
+
end
|
108
|
+
|
109
|
+
# there is a strange ruby bug about closing a connection....this catches it.
|
110
|
+
it "should raise NoMethodError if a non-method is called" do
|
111
|
+
http.should_receive(:request).and_raise(NoMethodError)
|
112
|
+
lambda {
|
113
|
+
subject.execute client, request_context
|
114
|
+
}.should raise_error(NoMethodError, /NoMethodError/)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should raise NoMethodError if a non-method is called" do
|
118
|
+
error = NoMethodError.new("undefined method `closed?' for nil:NilClass")
|
119
|
+
http.should_receive(:request).and_raise(error)
|
120
|
+
lambda {
|
121
|
+
subject.execute client, request_context
|
122
|
+
}.should raise_error(Errno::ECONNREFUSED)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "basic auth support" do
|
128
|
+
let(:http) { mock(Net::HTTP).as_null_object }
|
129
|
+
|
130
|
+
before do
|
131
|
+
Net::HTTP.stub(:new) { http }
|
132
|
+
end
|
133
|
+
|
134
|
+
it "sets the authorization header" do
|
135
|
+
http.should_receive(:request) do |request|
|
136
|
+
request.fetch('authorization').should == "Basic #{Base64.encode64("joe:pass")}".strip
|
137
|
+
mock(Net::HTTPResponse).as_null_object
|
138
|
+
end
|
139
|
+
Koha::Connection.new.execute nil, :uri => URI.parse("http://joe:pass@localhost/koha/rest.pl"), :method => :get
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "proxy support" do
|
144
|
+
it "set the proxy" do
|
145
|
+
@mock_proxy = mock('Proxy')
|
146
|
+
@mock_proxy.should_receive(:new).with("localhost", 80).and_return( mock(Net::HTTP).as_null_object)
|
147
|
+
Net::HTTP.should_receive(:Proxy).with("proxy", 80, nil, nil).and_return(@mock_proxy)
|
148
|
+
Koha::Connection.new.execute nil, :uri => URI.parse("http://localhost/koha/rest.pl"), :method => :get, :proxy => URI.parse("http://proxy/pass.pl")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe "Koha::Uri" do
|
3
|
+
|
4
|
+
context "class-level methods" do
|
5
|
+
|
6
|
+
let(:uri){ Koha::Uri }
|
7
|
+
|
8
|
+
it "should return a URI object with a trailing slash" do
|
9
|
+
u = uri.create 'http://koha.org'
|
10
|
+
u.path[0].should == ?/
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the bytesize of a string" do
|
14
|
+
uri.bytesize("test").should == 4
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
context "escape_query_value" do
|
20
|
+
|
21
|
+
it 'should escape &' do
|
22
|
+
uri.to_params(:biblionumber => "&").should == 'biblionumber=%26'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should escape &' do
|
26
|
+
uri.to_params({:biblionumber => "&" }, false).should == 'biblionumber=&'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should escape &' do
|
30
|
+
uri.to_params(:biblionumber => [1,2,3] ).should == 'biblionumber=1+2+3'
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
it 'should convert spaces to +' do
|
35
|
+
uri.to_params(:biblionumber => "me and you").should == 'biblionumber=me+and+you'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should escape comlex queries, part 1' do
|
39
|
+
my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'}
|
40
|
+
expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue'
|
41
|
+
uri.to_params(my_params).should == expected
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should escape complex queries, part 2' do
|
45
|
+
my_params = {'q' => '+popularity:[10 TO *] +section:0'}
|
46
|
+
expected = 'q=%2Bpopularity%3A%5B10+TO+%2A%5D+%2Bsection%3A0'
|
47
|
+
uri.to_params(my_params).should == expected
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should escape properly' do
|
51
|
+
uri.escape_query_value('+').should == '%2B'
|
52
|
+
uri.escape_query_value('This is a test').should == 'This+is+a+test'
|
53
|
+
uri.escape_query_value('<>/\\').should == '%3C%3E%2F%5C'
|
54
|
+
uri.escape_query_value('"').should == '%22'
|
55
|
+
uri.escape_query_value(':').should == '%3A'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should escape brackets' do
|
59
|
+
uri.escape_query_value('{').should == '%7B'
|
60
|
+
uri.escape_query_value('}').should == '%7D'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should escape exclamation marks!' do
|
64
|
+
uri.escape_query_value('!').should == '%21'
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tasks/spec.rake
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require 'rspec'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
namespace :spec do
|
6
|
+
|
7
|
+
desc 'run api specs (mock out Koha dependency)'
|
8
|
+
RSpec::Core::RakeTask.new(:api) do |t|
|
9
|
+
|
10
|
+
t.pattern = [File.join('spec', 'spec_helper.rb')]
|
11
|
+
t.pattern += FileList[File.join('spec', 'api', '**', '*_spec.rb')]
|
12
|
+
|
13
|
+
t.verbose = true
|
14
|
+
t.rspec_opts = ['--color']
|
15
|
+
end
|
16
|
+
=begin
|
17
|
+
desc 'run integration specs'
|
18
|
+
RSpec::Core::RakeTask.new(:integration) do |t|
|
19
|
+
|
20
|
+
t.pattern = [File.join('spec', 'spec_helper.rb')]
|
21
|
+
t.pattern += FileList[File.join('spec', 'integration', '**', '*_spec.rb')]
|
22
|
+
|
23
|
+
t.verbose = true
|
24
|
+
t.rspec_opts = ['--color']
|
25
|
+
end
|
26
|
+
=end
|
27
|
+
|
28
|
+
end
|
data/tasks/yard.rake
ADDED
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: koha
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- chris fitzpatrick
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: simplecov
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.7.1
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.7.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: yard
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.8.4.1
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.8.4.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: webmock
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.9.3
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.9.3
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 10.0.3
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 10.0.3
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rdoc
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 3.9.5
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 3.9.5
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.6.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.6.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: redcarpet
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: ! 'Easy interface for the Koha ILSDI API (https://github.com/Koha-Community/Koha/blob/master/C4/ILSDI/Services.pm) '
|
127
|
+
email:
|
128
|
+
- chrisfitzpat@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .travis.yml
|
135
|
+
- CHANGELOG
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- koha.gemspec
|
141
|
+
- lib/koha.rb
|
142
|
+
- lib/koha/client.rb
|
143
|
+
- lib/koha/connection.rb
|
144
|
+
- lib/koha/error.rb
|
145
|
+
- lib/koha/uri.rb
|
146
|
+
- lib/koha/version.rb
|
147
|
+
- spec/api/client_spec.rb
|
148
|
+
- spec/api/connection_spec.rb
|
149
|
+
- spec/api/uri_spec.rb
|
150
|
+
- spec/spec_helper.rb
|
151
|
+
- tasks/spec.rake
|
152
|
+
- tasks/yard.rake
|
153
|
+
homepage:
|
154
|
+
licenses: []
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ! '>='
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
|
+
none: false
|
167
|
+
requirements:
|
168
|
+
- - ! '>='
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
requirements: []
|
172
|
+
rubyforge_project:
|
173
|
+
rubygems_version: 1.8.24
|
174
|
+
signing_key:
|
175
|
+
specification_version: 3
|
176
|
+
summary: A Ruby client for Koha ILSDI interface
|
177
|
+
test_files: []
|
178
|
+
has_rdoc:
|