koha 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/cfitz/koha.png?branch=master)](https://travis-ci.org/cfitz/koha) [![Code Climate](https://codeclimate.com/github/cfitz/koha.png)](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:
|