ezid-client 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
-
|