ezid-client 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -0
- data/README.md +38 -59
- data/Rakefile +0 -7
- data/VERSION +1 -1
- data/ezid-client.gemspec +4 -2
- data/lib/ezid/client.rb +70 -100
- data/lib/ezid/configuration.rb +27 -12
- data/lib/ezid/identifier.rb +170 -0
- data/lib/ezid/metadata.rb +52 -94
- data/lib/ezid/metadata_elements.rb +89 -0
- data/lib/ezid/request.rb +20 -34
- data/lib/ezid/response.rb +40 -26
- data/lib/ezid/session.rb +2 -8
- data/lib/ezid/status.rb +23 -0
- data/spec/lib/ezid/client_spec.rb +136 -45
- data/spec/lib/ezid/identifier_spec.rb +203 -0
- data/spec/lib/ezid/metadata_spec.rb +200 -41
- data/spec/spec_helper.rb +7 -10
- metadata +32 -43
- data/lib/ezid/api.rb +0 -67
- data/lib/ezid/logger.rb +0 -31
- data/lib/ezid/test_helper.rb +0 -22
data/lib/ezid/request.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "delegate"
|
1
2
|
require "uri"
|
2
3
|
require "net/http"
|
3
4
|
|
@@ -5,50 +6,35 @@ module Ezid
|
|
5
6
|
#
|
6
7
|
# A request to the EZID service.
|
7
8
|
#
|
8
|
-
# @note A Request should only be created by an Ezid::Client instance.
|
9
9
|
# @api private
|
10
|
-
class Request
|
10
|
+
class Request < SimpleDelegator
|
11
11
|
|
12
|
-
|
12
|
+
HOST = "https://ezid.cdlib.org"
|
13
13
|
CHARSET = "UTF-8"
|
14
14
|
CONTENT_TYPE = "text/plain"
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
http_method, path, query = Api.send(*args)
|
21
|
-
@uri = URI::HTTPS.build(host: EZID_HOST, path: path, query: query)
|
22
|
-
@http_request = Net::HTTP.const_get(http_method).new(uri)
|
23
|
-
@http_request.set_content_type(CONTENT_TYPE, charset: CHARSET)
|
16
|
+
def self.execute(*args)
|
17
|
+
request = new(*args)
|
18
|
+
yield request if block_given?
|
19
|
+
request.execute
|
24
20
|
end
|
25
21
|
|
26
|
-
#
|
27
|
-
# @
|
28
|
-
def
|
29
|
-
Net::HTTP.
|
30
|
-
|
31
|
-
|
22
|
+
# @param method [Symbol] the Net::HTTP constant for the request method
|
23
|
+
# @param path [String] the uri path (including query string, if any)
|
24
|
+
def initialize(method, path)
|
25
|
+
http_method = Net::HTTP.const_get(method)
|
26
|
+
uri = URI.parse([HOST, path].join)
|
27
|
+
super(http_method.new(uri))
|
28
|
+
set_content_type(CONTENT_TYPE, charset: CHARSET)
|
32
29
|
end
|
33
30
|
|
34
|
-
#
|
35
|
-
# @
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# @option opts [String] :password password for basic auth
|
40
|
-
def add_authentication(opts={})
|
41
|
-
if opts[:cookie]
|
42
|
-
http_request["Cookie"] = opts[:cookie]
|
43
|
-
else
|
44
|
-
http_request.basic_auth(opts[:user], opts[:password])
|
31
|
+
# Executes the request and returns the response
|
32
|
+
# @return [Ezid::Response] the response
|
33
|
+
def execute
|
34
|
+
http_response = Net::HTTP.start(uri.host, use_ssl: true) do |http|
|
35
|
+
http.request(__getobj__)
|
45
36
|
end
|
46
|
-
|
47
|
-
|
48
|
-
# Adds EZID metadata (if any) to the request body
|
49
|
-
# @param metadata [Ezid::Metadata] the metadata to add
|
50
|
-
def add_metadata(metadata)
|
51
|
-
http_request.body = metadata.to_anvl unless metadata.empty?
|
37
|
+
Response.new(http_response)
|
52
38
|
end
|
53
39
|
|
54
40
|
end
|
data/lib/ezid/response.rb
CHANGED
@@ -4,7 +4,6 @@ module Ezid
|
|
4
4
|
#
|
5
5
|
# A response from the EZID service.
|
6
6
|
#
|
7
|
-
# @note A Response should only be created by an Ezid::Client instance.
|
8
7
|
# @api private
|
9
8
|
class Response < SimpleDelegator
|
10
9
|
|
@@ -14,6 +13,21 @@ module Ezid
|
|
14
13
|
# Error response status
|
15
14
|
ERROR = "error"
|
16
15
|
|
16
|
+
IDENTIFIER_RE = /^(doi|ark|urn):[^\s]+/
|
17
|
+
SHADOW_ARK_RE = /\| (ark:[^\s]+)/
|
18
|
+
|
19
|
+
def id
|
20
|
+
@id ||= IDENTIFIER_RE.match(message)[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
def shadow_ark
|
24
|
+
@shadow_ark ||= SHADOW_ARK_RE.match(message)[1]
|
25
|
+
end
|
26
|
+
|
27
|
+
def metadata
|
28
|
+
content[1]
|
29
|
+
end
|
30
|
+
|
17
31
|
# The response status -- "success" or "error"
|
18
32
|
# @return [String] the status
|
19
33
|
def status
|
@@ -23,7 +37,7 @@ module Ezid
|
|
23
37
|
# The status line of the response
|
24
38
|
# @return [String] the status line
|
25
39
|
def status_line
|
26
|
-
content
|
40
|
+
content[0]
|
27
41
|
end
|
28
42
|
|
29
43
|
# The body of the response split into: status line and rest of body
|
@@ -32,45 +46,45 @@ module Ezid
|
|
32
46
|
@content ||= body.split(/\r?\n/, 2)
|
33
47
|
end
|
34
48
|
|
35
|
-
#
|
36
|
-
# @return [
|
37
|
-
def metadata
|
38
|
-
return @metadata if @metadata
|
39
|
-
if success? && identifier_uri?
|
40
|
-
@metadata = Metadata.new(content.last)
|
41
|
-
end
|
42
|
-
@metadata
|
43
|
-
end
|
44
|
-
|
45
|
-
# The identifier string parsed out of the response
|
46
|
-
# @return [String] the identifier
|
47
|
-
def identifier
|
48
|
-
message.split(/\s/).first if success? && identifier_uri?
|
49
|
-
end
|
50
|
-
|
51
|
-
def identifier_uri?
|
52
|
-
( uri.path =~ /^\/(id|shoulder)\// ) && true
|
53
|
-
end
|
54
|
-
|
49
|
+
# The outcome of the request - "success" or "error"
|
50
|
+
# @return [String] the outcome
|
55
51
|
def outcome
|
56
52
|
status.first
|
57
53
|
end
|
58
54
|
|
55
|
+
# The EZID status message
|
56
|
+
# @return [String] the message
|
59
57
|
def message
|
60
58
|
status.last
|
61
59
|
end
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
61
|
+
# Whether the outcome was an error
|
62
|
+
# @return [Boolean]
|
67
63
|
def error?
|
68
64
|
outcome == ERROR
|
69
65
|
end
|
70
66
|
|
67
|
+
# Whether the outcome was a success
|
68
|
+
# @return [Boolean]
|
71
69
|
def success?
|
72
70
|
outcome == SUCCESS
|
73
71
|
end
|
74
72
|
|
73
|
+
# Returns an exception instance if there was an error
|
74
|
+
# @return [Ezid::Error] the exception
|
75
|
+
def exception
|
76
|
+
@exception ||= (error? && Error.new(message))
|
77
|
+
end
|
78
|
+
|
79
|
+
# The URI path of the request
|
80
|
+
# @return [String] the path
|
81
|
+
def uri_path
|
82
|
+
__getobj__.uri.path
|
83
|
+
end
|
84
|
+
|
85
|
+
def cookie
|
86
|
+
self["Set-Cookie"].split(";").first rescue nil
|
87
|
+
end
|
88
|
+
|
75
89
|
end
|
76
90
|
end
|
data/lib/ezid/session.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
require "uri"
|
2
|
-
require "net/http"
|
3
|
-
|
4
|
-
require_relative "request"
|
5
|
-
|
6
1
|
module Ezid
|
7
2
|
#
|
8
3
|
# An EZID session
|
@@ -20,9 +15,8 @@ module Ezid
|
|
20
15
|
super.sub(/@cookie="[^\"]+"/, "OPEN")
|
21
16
|
end
|
22
17
|
|
23
|
-
def open(
|
24
|
-
|
25
|
-
@cookie = response.cookie if response.cookie
|
18
|
+
def open(cookie)
|
19
|
+
@cookie = cookie
|
26
20
|
end
|
27
21
|
|
28
22
|
def close
|
data/lib/ezid/status.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ezid
|
2
|
+
class Status < SimpleDelegator
|
3
|
+
|
4
|
+
SUBSYSTEMS = %w( noid ldap datacite )
|
5
|
+
|
6
|
+
SUBSYSTEMS.each do |s|
|
7
|
+
define_method(s) { subsystems[s] || "not checked" }
|
8
|
+
end
|
9
|
+
|
10
|
+
def subsystems
|
11
|
+
return {} unless content[1]
|
12
|
+
content[1].split(/\r?\n/).each_with_object({}) do |line, memo|
|
13
|
+
subsystem, status = line.split(": ", 2)
|
14
|
+
memo[subsystem] = status
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def up?
|
19
|
+
success?
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -1,90 +1,181 @@
|
|
1
1
|
module Ezid
|
2
2
|
RSpec.describe Client do
|
3
|
+
|
3
4
|
describe "initialization" do
|
4
5
|
describe "without a block" do
|
5
|
-
it "should not
|
6
|
-
|
6
|
+
it "should not login" do
|
7
|
+
expect_any_instance_of(described_class).not_to receive(:login)
|
8
|
+
described_class.new
|
7
9
|
end
|
8
10
|
end
|
9
|
-
describe "with a block", :
|
10
|
-
it "should
|
11
|
+
describe "with a block", type: :feature do
|
12
|
+
it "should wrap the block in a session" do
|
13
|
+
expect_any_instance_of(described_class).to receive(:login).and_call_original
|
14
|
+
expect_any_instance_of(described_class).to receive(:logout).and_call_original
|
11
15
|
described_class.new do |client|
|
12
|
-
expect(client).to
|
16
|
+
expect(client.session).to be_open
|
13
17
|
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
it "should
|
21
|
-
expect(subject).to
|
21
|
+
|
22
|
+
describe "authentication", type: :feature do
|
23
|
+
describe "#login" do
|
24
|
+
it "should open a session" do
|
25
|
+
expect(subject.session).to be_closed
|
26
|
+
subject.login
|
27
|
+
expect(subject.session).to be_open
|
22
28
|
end
|
23
29
|
end
|
24
|
-
describe "
|
30
|
+
describe "#logout" do
|
25
31
|
before { subject.login }
|
26
|
-
it "should
|
32
|
+
it "should close the session" do
|
33
|
+
expect(subject.session).to be_open
|
27
34
|
subject.logout
|
28
|
-
expect(subject).
|
35
|
+
expect(subject.session).to be_closed
|
36
|
+
end
|
37
|
+
end
|
38
|
+
describe "without a session" do
|
39
|
+
it "should send the user name and password" do
|
40
|
+
expect_any_instance_of(Net::HTTP::Post).to receive(:basic_auth).with(subject.user, subject.password).and_call_original
|
41
|
+
subject.mint_identifier(ARK_SHOULDER)
|
29
42
|
end
|
30
43
|
end
|
31
44
|
end
|
32
|
-
|
33
|
-
|
45
|
+
|
46
|
+
describe "#create_identifier" do
|
47
|
+
let(:id) { "ark:/99999/fk4fn19h88" }
|
48
|
+
let(:http_response) { double(body: "success: ark:/99999/fk4fn19h88") }
|
49
|
+
let(:stub_response) { Response.new(http_response) }
|
50
|
+
before do
|
51
|
+
allow(Request).to receive(:execute) { stub_response }
|
52
|
+
end
|
53
|
+
subject { described_class.new.create_identifier(id) }
|
54
|
+
it "should be a success" do
|
55
|
+
expect(subject).to be_success
|
56
|
+
expect(subject.id).to eq(id)
|
57
|
+
end
|
34
58
|
end
|
35
|
-
|
59
|
+
|
60
|
+
describe "#mint_identifier" do
|
61
|
+
before { allow(Request).to receive(:execute) { stub_response } }
|
36
62
|
describe "which is an ARK" do
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
expect(
|
63
|
+
let(:stub_response) { Response.new(double(body: "success: ark:/99999/fk4fn19h88")) }
|
64
|
+
subject { described_class.new.mint_identifier(ARK_SHOULDER) }
|
65
|
+
it "should be a succes" do
|
66
|
+
expect(subject).to be_success
|
67
|
+
expect(subject.id).to eq("ark:/99999/fk4fn19h88")
|
41
68
|
end
|
42
69
|
end
|
43
70
|
describe "which is a DOI" do
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
71
|
+
let(:http_response) { double(body: "success: doi:10.5072/FK2TEST | ark:/99999/fk4fn19h88") }
|
72
|
+
let(:stub_response) { Response.new(http_response) }
|
73
|
+
let(:metadata) do
|
74
|
+
<<-EOS
|
75
|
+
datacite.title: Test
|
76
|
+
datacite.creator: Duke
|
77
|
+
datacite.publisher: Duke
|
78
|
+
datacite.publicationyear: 2014
|
79
|
+
datacite.resourcetype: Other
|
80
|
+
EOS
|
81
|
+
end
|
82
|
+
subject { described_class.new.mint_identifier(DOI_SHOULDER, metadata) }
|
83
|
+
it "should be a sucess" do
|
84
|
+
expect(subject).to be_success
|
85
|
+
expect(subject.id).to eq("doi:10.5072/FK2TEST")
|
86
|
+
expect(subject.shadow_ark).to eq("ark:/99999/fk4fn19h88")
|
49
87
|
end
|
50
88
|
end
|
51
89
|
end
|
52
|
-
|
90
|
+
|
91
|
+
describe "#get_identifier_metadata" do
|
92
|
+
let(:stub_response) do
|
93
|
+
Response.new(double(body: <<-EOS
|
94
|
+
success: ark:/99999/fk4fn19h88
|
95
|
+
_updated: 1416507086
|
96
|
+
_target: http://ezid.cdlib.org/id/ark:/99999/fk4fn19h88
|
97
|
+
_profile: erc
|
98
|
+
_ownergroup: apitest
|
99
|
+
_owner: apitest
|
100
|
+
_export: yes
|
101
|
+
_created: 1416507086
|
102
|
+
_status: public
|
103
|
+
EOS
|
104
|
+
))
|
105
|
+
end
|
53
106
|
before do
|
54
|
-
|
107
|
+
allow(Request).to receive(:execute) { stub_response }
|
55
108
|
end
|
56
|
-
|
57
|
-
|
58
|
-
expect(
|
109
|
+
subject { described_class.new.get_identifier_metadata("ark:/99999/fk4fn19h88") }
|
110
|
+
it "should retrieve the metadata" do
|
111
|
+
expect(subject.metadata).to eq <<-EOS
|
112
|
+
_updated: 1416507086
|
113
|
+
_target: http://ezid.cdlib.org/id/ark:/99999/fk4fn19h88
|
114
|
+
_profile: erc
|
115
|
+
_ownergroup: apitest
|
116
|
+
_owner: apitest
|
117
|
+
_export: yes
|
118
|
+
_created: 1416507086
|
119
|
+
_status: public
|
120
|
+
EOS
|
59
121
|
end
|
60
122
|
end
|
61
|
-
|
123
|
+
|
124
|
+
describe "#modify_identifier", type: :feature do
|
62
125
|
before do
|
63
|
-
@
|
126
|
+
@id = described_class.new.mint_identifier(ARK_SHOULDER).id
|
64
127
|
end
|
128
|
+
subject { described_class.new.modify_identifier(@id, "dc.title" => "Test") }
|
65
129
|
it "should update the metadata" do
|
66
|
-
subject.
|
67
|
-
response =
|
68
|
-
expect(response.
|
130
|
+
expect(subject).to be_success
|
131
|
+
response = described_class.new.get_identifier_metadata(@id)
|
132
|
+
expect(response.metadata).to match(/dc.title: Test/)
|
69
133
|
end
|
70
134
|
end
|
71
|
-
|
135
|
+
|
136
|
+
describe "#delete_identifier", type: :feature do
|
72
137
|
before do
|
73
|
-
@
|
138
|
+
@id = described_class.new.mint_identifier(ARK_SHOULDER, "_status" => "reserved").id
|
74
139
|
end
|
140
|
+
subject { described_class.new.delete_identifier(@id) }
|
75
141
|
it "should delete the identifier" do
|
76
|
-
|
77
|
-
expect(
|
78
|
-
expect { subject.get_identifier_metadata(@identifier) }.to raise_error
|
142
|
+
expect(subject).to be_success
|
143
|
+
expect { described_class.new.get_identifier_metadata(@id) }.to raise_error
|
79
144
|
end
|
80
145
|
end
|
81
|
-
|
146
|
+
|
147
|
+
describe "server status" do
|
148
|
+
let(:http_response) do
|
149
|
+
double(body: <<-EOS
|
150
|
+
success: EZID is up
|
151
|
+
noid: up
|
152
|
+
ldap: up
|
153
|
+
EOS
|
154
|
+
)
|
155
|
+
end
|
156
|
+
let(:stub_response) { Response.new(http_response) }
|
157
|
+
before do
|
158
|
+
allow(Request).to receive(:execute) { stub_response }
|
159
|
+
end
|
160
|
+
subject { described_class.new.server_status("*") }
|
82
161
|
it "should report the status of EZID and subsystems" do
|
83
|
-
|
84
|
-
expect(
|
85
|
-
expect(
|
162
|
+
expect(subject).to be_success
|
163
|
+
expect(subject).to be_up
|
164
|
+
expect(subject.message).to eq("EZID is up")
|
165
|
+
expect(subject.noid).to eq("up")
|
166
|
+
expect(subject.ldap).to eq("up")
|
167
|
+
expect(subject.datacite).to eq("not checked")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "error handling" do
|
172
|
+
let(:stub_response) { Response.new(body: "error: bad request - no such identifier") }
|
173
|
+
before do
|
174
|
+
allow(Request).to receive(:execute) { stub_response }
|
175
|
+
end
|
176
|
+
it "should raise an exception" do
|
177
|
+
expect { subject.get_identifier_metadata("invalid") }.to raise_error
|
86
178
|
end
|
87
179
|
end
|
88
180
|
end
|
89
181
|
end
|
90
|
-
|