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.
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
- EZID_HOST = "ezid.cdlib.org"
12
+ HOST = "https://ezid.cdlib.org"
13
13
  CHARSET = "UTF-8"
14
14
  CONTENT_TYPE = "text/plain"
15
15
 
16
- attr_reader :http_request, :uri, :operation
17
-
18
- def initialize(*args)
19
- @operation = args
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
- # Executes the request and returns the HTTP response
27
- # @return [Net::HTTPResponse] the response
28
- def execute
29
- Net::HTTP.start(uri.host, use_ssl: true) do |http|
30
- http.request(http_request)
31
- end
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
- # Adds authentication data to the request
35
- # @param opts [Hash] the options.
36
- # Must include either: `:cookie`, or: `:user` and `:password`.
37
- # @option opts [String] :cookie a session cookie
38
- # @option opts [String] :user user name for basic auth
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
- end
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.first
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
- # Metadata (if any) parsed out of the response
36
- # @return [Ezid::Metadata] the metadata
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
- def cookie
64
- self["Set-Cookie"].split(";").first rescue nil
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(response)
24
- # XXX raise exception if no cookie?
25
- @cookie = response.cookie if response.cookie
18
+ def open(cookie)
19
+ @cookie = cookie
26
20
  end
27
21
 
28
22
  def close
@@ -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 be logged in" do
6
- expect(subject).not_to be_logged_in
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", :vcr do
10
- it "should be logged in" do
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 be_logged_in
16
+ expect(client.session).to be_open
13
17
  end
14
18
  end
15
19
  end
16
20
  end
17
- describe "authentication", :vcr do
18
- describe "logging in" do
19
- before { subject.login }
20
- it "should be logged in" do
21
- expect(subject).to be_logged_in
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 "logging out" do
30
+ describe "#logout" do
25
31
  before { subject.login }
26
- it "should not be logged in" do
32
+ it "should close the session" do
33
+ expect(subject.session).to be_open
27
34
  subject.logout
28
- expect(subject).not_to be_logged_in
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
- describe "creating an identifier", :vcr do
33
- # TODO
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
- describe "minting an identifier", :vcr do
59
+
60
+ describe "#mint_identifier" do
61
+ before { allow(Request).to receive(:execute) { stub_response } }
36
62
  describe "which is an ARK" do
37
- it "should be a success" do
38
- response = subject.mint_identifier(ARK_SHOULDER)
39
- expect(response).to be_success
40
- expect(response.message).to match(/#{ARK_SHOULDER}/)
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
- it "should be a sucess" do
45
- response = subject.mint_identifier(DOI_SHOULDER, doi_metadata)
46
- expect(response).to be_success
47
- expect(response.message).to match(/#{DOI_SHOULDER}/)
48
- expect(response.message).to match(/\| ark:/)
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
- describe "getting identifier metadata" do
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
- @identifier = subject.mint_identifier(ARK_SHOULDER).identifier
107
+ allow(Request).to receive(:execute) { stub_response }
55
108
  end
56
- it "should return the metadata" do
57
- response = subject.get_identifier_metadata(@identifier)
58
- expect(response.body).to match(/_status: public/)
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
- describe "modifying an identifier" do
123
+
124
+ describe "#modify_identifier", type: :feature do
62
125
  before do
63
- @identifier = subject.mint_identifier(ARK_SHOULDER).identifier
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.modify_identifier(@identifier, "dc.title" => "Test")
67
- response = subject.get_identifier_metadata(@identifier)
68
- expect(response.body).to match(/dc.title: Test/)
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
- describe "deleting an identifier" do
135
+
136
+ describe "#delete_identifier", type: :feature do
72
137
  before do
73
- @identifier = subject.mint_identifier(ARK_SHOULDER, "_status" => "reserved").identifier
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
- response = subject.delete_identifier(@identifier)
77
- expect(response).to be_success
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
- describe "server status", :vcr do
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
- response = subject.server_status("*")
84
- expect(response).to be_success
85
- expect(response.message).to eq("EZID is up")
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
-