archivesspace-client 0.4.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cb687dbc7f3a7a89938658d7024ab955b82004b42e25992b848fb956307d91d
4
- data.tar.gz: a3ae5bbd93f28c043cbe15d8f4d0f0f3010171710f212116da3c60eecab4f816
3
+ metadata.gz: 324a811c6e310286a30371cda14266b61f11570373aa20baf61b1f54b8e3c8d3
4
+ data.tar.gz: 319354d94ec1fb765a8a811f2ff8273c21342f40fa8e3a5e3c122b4b9af85b92
5
5
  SHA512:
6
- metadata.gz: ce3fb2a7fdea4c8fcd40b61f306581f9dbbb2ae2f83b1096500cb571ae6abcc82291d8493bbfff18b56284c33b8507e46f5eed97c25b81e4d4da6edcbf03bbef
7
- data.tar.gz: 04f948ae7c1060b0872e32051a58936ce792190545841ad5153d741ce3af0d8a43f78db7c22c1486aa1d2bef02c368341fb64073a56d0b76f23c14685bfa10d8
6
+ metadata.gz: 1ecd05a580e11e19fd50db9b0f0d4ede6ff816d36d8789d50420a1d1275bf9247c5172b932f3ad9627e96655ce27f919d35e6737816650541615cf5efea22c95
7
+ data.tar.gz: 679be0bac6c9a81b685ed233e457d98670e00f834946f57d7ed46091bfd83040c0ee12d0e196bddd00a9bfb88fefc8b0c06170dfd294652b4322b958bfdac4df
@@ -3,8 +3,12 @@ on: [pull_request]
3
3
 
4
4
  jobs:
5
5
  tests:
6
- name: Tests
6
+ name: Tests (Ruby ${{ matrix.ruby }})
7
7
  runs-on: ubuntu-latest
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: ["3.4", "4.0"]
8
12
  steps:
9
13
  - name: Checkout code
10
14
  uses: actions/checkout@v4
@@ -12,6 +16,7 @@ jobs:
12
16
  - name: Setup Ruby and install gems
13
17
  uses: ruby/setup-ruby@v1
14
18
  with:
19
+ ruby-version: ${{ matrix.ruby }}
15
20
  bundler-cache: true
16
21
 
17
22
  - name: Lint
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.2
1
+ 4.0.1
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Archivesspace Client
1
+ # ArchivesSpace Client
2
2
 
3
3
  Interact with ArchivesSpace via the API.
4
4
 
@@ -6,17 +6,18 @@ Interact with ArchivesSpace via the API.
6
6
 
7
7
  * [Installation](#installation)
8
8
  * [Usage](#usage)
9
- + [Configuring a client](#configuring-a-client)
10
- - [Default configuration](#default-configuration)
11
- - [Custom configuration, on the fly](#custom-configuration-on-the-fly)
12
- - [Custom configuration, stored for use with CLI or console](#custom-configuration-stored-for-use-with-cli-or-console)
13
- + [Making basic requests](#making-basic-requests)
14
- + [Setting a repository context](#setting-a-repository-context)
9
+ * [Configuring a client](#configuring-a-client)
10
+ * [Default configuration](#default-configuration)
11
+ * [Custom configuration, on the fly](#custom-configuration-on-the-fly)
12
+ * [Custom configuration, stored for use with CLI or console](#custom-configuration-stored-for-use-with-cli-or-console)
13
+ * [Making basic requests](#making-basic-requests)
14
+ * [Setting a repository context](#setting-a-repository-context)
15
15
  * [Templates](#templates)
16
16
  * [CLI](#cli)
17
17
  * [Console usage](#console-usage)
18
18
  * [Development](#development)
19
19
  * [Publishing](#publishing)
20
+ * [Changelog](#changelog)
20
21
  * [Contributing](#contributing)
21
22
  * [License](#license)
22
23
 
@@ -56,7 +57,7 @@ Create client with default settings (`localhost:8089`, `admin`, `admin`):
56
57
  client = ArchivesSpace::Client.new.login
57
58
  ```
58
59
 
59
- #### Custom configuration, on the fly
60
+ #### Custom configuration
60
61
 
61
62
  ```ruby
62
63
  config = ArchivesSpace::Configuration.new({
@@ -72,13 +73,11 @@ config = ArchivesSpace::Configuration.new({
72
73
  client = ArchivesSpace::Client.new(config).login
73
74
  ```
74
75
 
75
- **NOTE:** `ArchivesSpace::Configuration` allows you to set a `base_repo` value as well, but if this value is set in your config at the start, calls to API endpoints that do not include a repository id in the URI may not work correctly. It is recommended you set/unset the client repository as needed during use via the `#repository` method as described below. If you must set `base_repo` in the config used to create your client, note that the value should be like: "repositories/2", and not just the integer repository id.
76
-
77
76
  #### Custom configuration, stored for use with CLI or console
78
77
 
79
78
  Create a file containing JSON config data like:
80
79
 
81
- ```
80
+ ```json
82
81
  {
83
82
  "base_uri": "http://localhost:4567",
84
83
  "username": "admin",
@@ -95,11 +94,10 @@ The CLI and console commands will, by default, look for this stored config at `~
95
94
 
96
95
  However, you may also set a custom location for the file by setting an ASCLIENT_CFG environment variable. This is handy if you prefer to use [XDG Base Directory Specification](https://xdgbasedirectoryspecification.com/), or have other opinions about where such config should live:
97
96
 
98
- ```
97
+ ```bash
99
98
  export ASCLIENT_CFG="$HOME/.config/archivesspace/client.json"
100
99
  ```
101
100
 
102
-
103
101
  ### Making basic requests
104
102
 
105
103
  The client responds to the standard request methods:
@@ -139,21 +137,33 @@ client.get('repositories/2/digital_objects', query: {page: 1})
139
137
  You can do:
140
138
 
141
139
  ```ruby
142
- client.repository(2)
143
- client.get('digital_objects', query: {page: 1})
144
- ```
140
+ client.repository(2) do
141
+ client.get('digital_objects', query: {page: 1})
142
+ end
145
143
 
144
+ # now back in the global scope
145
+ ```
146
146
 
147
- To reset:
147
+ Scopes restore the previous context on exit (even if the block raises) and can
148
+ be nested:
148
149
 
149
150
  ```ruby
150
- client.repository(nil)
151
+ client.repository(2) do
152
+ client.repository(3) do
153
+ client.resources # scoped to repo 3
154
+ end
155
+ client.resources # back to repo 2
156
+ end
157
+
158
+ # now back in the global scope
151
159
  ```
152
160
 
153
- Or:
161
+ You can also set the scope persistently (without a block) and clear it later:
154
162
 
155
163
  ```ruby
156
- client.use_global_repository
164
+ client.repository(2)
165
+ client.resources
166
+ client.use_global_repository # or: client.repository(nil)
157
167
  ```
158
168
 
159
169
  ## Templates
@@ -221,7 +231,7 @@ cd path/to/archivesspace-client
221
231
 
222
232
  An IRB session opens. Entering the following should give you the backend version of the ArchivesSpace instance your stored custom config points to:
223
233
 
224
- ```
234
+ ```ruby
225
235
  @client.backend_version
226
236
  ```
227
237
 
@@ -248,9 +258,26 @@ bundle exec rake
248
258
  When an updated version (`lib/archivesspace/client/version.rb`) is merged into the
249
259
  main/master branch a new release will be built and published.
250
260
 
261
+ ## Changelog
262
+
263
+ ### 0.5.0
264
+
265
+ Breaking changes:
266
+
267
+ * Removed the `base_repo` configuration option. Use `client.repository(id)` to
268
+ scope requests to a repository instead, either persistently or with a
269
+ block that auto-restores the previous scope.
270
+ * `client.repository(id)` with a block now saves and restores the previous
271
+ context (including when nested or when the block raises), instead of always
272
+ resetting to the global scope.
273
+ * Login failure now raises `ArchivesSpace::AuthenticationError` (was
274
+ `ConnectionError`). `ConnectionError` has been removed.
275
+ * `Client.new` raises `ArchivesSpace::ConfigurationError` (was `RuntimeError`)
276
+ when given a non-`Configuration` argument.
277
+
251
278
  ## Contributing
252
279
 
253
- Bug reports and pull requests are welcome on GitHub at https://github.com/lyrasis/archivesspace-client.
280
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/lyrasis/archivesspace-client>.
254
281
 
255
282
  ## License
256
283
 
@@ -23,15 +23,14 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "capybara_discoball", "~> 0.1.0"
24
24
  spec.add_development_dependency "json_spec", "~> 1.1", ">= 1.1.5"
25
25
  spec.add_development_dependency "rake", "~> 13.0"
26
- spec.add_development_dependency "rspec", "3.6.0"
27
- spec.add_development_dependency "rubocop", "1.56"
28
- spec.add_development_dependency "standard", "1.31.0"
29
- spec.add_development_dependency "vcr", "6.2.0"
30
- spec.add_development_dependency "webmock", "3.19.1"
26
+ spec.add_development_dependency "rspec", "~> 3.13"
27
+ spec.add_development_dependency "rubocop", "~> 1.72"
28
+ spec.add_development_dependency "standard", "~> 1.44"
29
+ spec.add_development_dependency "vcr", "~> 6.3"
30
+ spec.add_development_dependency "webmock", "~> 3.24"
31
31
 
32
32
  spec.add_dependency "dry-cli", "~> 0.7"
33
33
  spec.add_dependency "httparty", "~> 0.14"
34
34
  spec.add_dependency "json", "~> 2.0"
35
- spec.add_dependency "nokogiri", "~> 1.10"
36
35
  spec.add_dependency "jbuilder", "~> 2.12"
37
36
  end
data/examples/export.rb CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
4
  require "archivesspace/client"
5
+ require "nokogiri"
5
6
 
6
7
  # official sandbox
7
8
  config = ArchivesSpace::Configuration.new(
8
9
  {
9
10
  base_uri: "https://test.archivesspace.org/staff/api",
10
- base_repo: "",
11
11
  username: "admin",
12
12
  password: "admin",
13
13
  page_size: 50,
@@ -18,17 +18,18 @@ config = ArchivesSpace::Configuration.new(
18
18
 
19
19
  client = ArchivesSpace::Client.new(config).login
20
20
  client.config.throttle = 0.5
21
- client.config.base_repo = "repositories/2"
22
21
 
23
22
  begin
24
- # date -d '2021-02-01 00:00:00' +'%s' # 1612166400
25
- client.resources(query: {modified_since: "1612166400"}).each do |resource|
26
- # for now we are just printing ...
27
- # but you would actually write to a zip file or whatever
28
- id = resource["uri"].split("/")[-1]
29
- opts = {include_unpublished: false}
30
- response = client.get("resource_descriptions/#{id}.xml", opts)
31
- puts Nokogiri::XML(response.body).to_xml
23
+ client.repository 2 do
24
+ # date -d '2021-02-01 00:00:00' +'%s' # 1612166400
25
+ client.resources(query: {modified_since: "1612166400"}).each do |resource|
26
+ # for now we are just printing ...
27
+ # but you would actually write to a zip file or whatever
28
+ id = resource["uri"].split("/")[-1]
29
+ opts = {include_unpublished: false}
30
+ response = client.get("resource_descriptions/#{id}.xml", opts)
31
+ puts Nokogiri::XML(response.body).to_xml
32
+ end
32
33
  end
33
34
  rescue ArchivesSpace::RequestError => e
34
35
  puts e.message
@@ -7,7 +7,6 @@ require "archivesspace/client"
7
7
  config = ArchivesSpace::Configuration.new(
8
8
  {
9
9
  base_uri: "https://sandbox.archivesspace.org/staff/api",
10
- base_repo: "",
11
10
  username: "admin",
12
11
  password: "admin",
13
12
  page_size: 50,
@@ -6,8 +6,7 @@ require "archivesspace/client"
6
6
  # official sandbox
7
7
  config = ArchivesSpace::Configuration.new(
8
8
  {
9
- base_uri: "https://sandbox.archivesspace.org/staff/api",
10
- base_repo: "",
9
+ base_uri: "https://test.archivesspace.org/staff/api",
11
10
  username: "admin",
12
11
  password: "admin",
13
12
  page_size: 50,
@@ -17,4 +16,16 @@ config = ArchivesSpace::Configuration.new(
17
16
  )
18
17
 
19
18
  client = ArchivesSpace::Client.new(config).login
19
+
20
+ # globally scoped
20
21
  puts client.get("version").body
22
+ puts client.get("repositories").body
23
+ puts client.all("users").map { |u| u["username"] }.to_a
24
+
25
+ # repo scoped
26
+ client.repository 2 do
27
+ puts client.get("accessions", query: {page: 1}).parsed["results"].map { |a| a["uri"] }.to_a
28
+ end
29
+
30
+ # globally scoped, full path arg
31
+ puts client.get("repositories/2/resources", query: {all_ids: true}).body
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+ require "archivesspace/client"
5
+
6
+ config = ArchivesSpace::Configuration.new(
7
+ {
8
+ base_uri: "https://test.archivesspace.org/staff/api",
9
+ username: "admin",
10
+ password: "admin",
11
+ page_size: 50,
12
+ throttle: 0,
13
+ verify_ssl: false
14
+ }
15
+ )
16
+
17
+ client = ArchivesSpace::Client.new(config).login
18
+ response = client.get("update-feed")
19
+ last_sequence = response.parsed[0]["sequence"] # get initial sequence value
20
+
21
+ # This is an example only, would also need to handle session/token expiration
22
+ loop do
23
+ puts "Using sequence: #{last_sequence}"
24
+ begin
25
+ response = client.get("update-feed", query: {last_sequence: last_sequence})
26
+ if response.result.success?
27
+ last_sequence = response.parsed.last["sequence"]
28
+ # do something with the response
29
+ puts response.parsed.to_json
30
+ end
31
+ rescue Net::ReadTimeout
32
+ # this is ok if no updates are made within the last 60 seconds
33
+ # (well, as defined by the timeout in client/AppConfig)
34
+ rescue => e
35
+ # some other kind of error occurred
36
+ puts e.message
37
+ break # or handle it in some other way
38
+ end
39
+ end
@@ -7,7 +7,6 @@ require "archivesspace/client"
7
7
  config = ArchivesSpace::Configuration.new(
8
8
  {
9
9
  base_uri: "https://sandbox.archivesspace.org/staff/api",
10
- base_repo: "",
11
10
  username: "admin",
12
11
  password: "admin",
13
12
  page_size: 50,
@@ -31,9 +30,10 @@ users_with_roles = {
31
30
  }
32
31
 
33
32
  begin
34
- client.config.base_repo = "repositories/2"
35
- results = client.group_user_assignment users_with_roles
36
- puts results.map(&:parsed)
33
+ client.repository 2 do
34
+ results = client.group_user_assignment users_with_roles
35
+ puts results.map(&:parsed)
36
+ end
37
37
  rescue ArchivesSpace::RequestError => e
38
38
  puts e.message
39
39
  end
@@ -4,15 +4,20 @@ module ArchivesSpace
4
4
  class Client
5
5
  include Pagination
6
6
  include Task
7
+
7
8
  attr_accessor :token
8
- attr_reader :config
9
+ attr_reader :config, :context
9
10
 
10
11
  NAME = "ArchivesSpaceClient"
12
+ TOKEN = "X-ArchivesSpace-Session"
11
13
 
12
14
  def initialize(config = Configuration.new)
13
- raise "Invalid configuration object" unless config.is_a? ArchivesSpace::Configuration
15
+ unless config.is_a? ArchivesSpace::Configuration
16
+ raise ConfigurationError, "expected ArchivesSpace::Configuration, got #{config.class}"
17
+ end
14
18
 
15
19
  @config = config
20
+ @context = nil
16
21
  @token = nil
17
22
  end
18
23
 
@@ -38,31 +43,36 @@ module ArchivesSpace
38
43
 
39
44
  # Scoping requests
40
45
  def repository(id)
41
- if id.nil?
42
- use_global_repository
43
- return
44
- end
46
+ return use_global_repository if id.nil?
45
47
 
46
48
  begin
47
49
  Integer(id)
48
- rescue
50
+ rescue ArgumentError, TypeError
49
51
  raise RepositoryIdError, "Invalid Repository id: #{id}"
50
52
  end
51
53
 
52
- @config.base_repo = "repositories/#{id}"
54
+ new_context = "repositories/#{id}"
55
+ return @context = new_context unless block_given?
56
+
57
+ previous = @context
58
+ @context = new_context
59
+ begin
60
+ yield
61
+ ensure
62
+ @context = previous
63
+ end
53
64
  end
54
65
 
55
66
  def use_global_repository
56
- @config.base_repo = ""
67
+ @context = nil
57
68
  end
58
69
 
59
70
  private
60
71
 
61
72
  def request(method, path, options = {})
62
73
  sleep config.throttle
63
- options[:headers] = {"X-ArchivesSpace-Session" => token} if token
64
- result = Request.new(config, method, path, options).execute
65
- Response.new result
74
+ options[:headers] = {TOKEN => token} if token
75
+ Request.new(context, config, method, path, options).execute
66
76
  end
67
77
  end
68
78
  end
@@ -2,27 +2,25 @@
2
2
 
3
3
  module ArchivesSpace
4
4
  class Configuration
5
- def defaults
6
- {
7
- base_uri: "http://localhost:8089",
8
- base_repo: "",
9
- debug: false,
10
- username: "admin",
11
- password: "admin",
12
- page_size: 50,
13
- throttle: 0,
14
- verify_ssl: true,
15
- timeout: 60
16
- }
17
- end
5
+ attr_accessor :base_uri, :debug, :username, :password,
6
+ :page_size, :throttle, :timeout, :verify_ssl
7
+
8
+ DEFAULTS = {
9
+ base_uri: "http://localhost:8089",
10
+ debug: false,
11
+ username: "admin",
12
+ password: "admin",
13
+ page_size: 50,
14
+ throttle: 0,
15
+ timeout: 60,
16
+ verify_ssl: true
17
+ }.freeze
18
18
 
19
19
  def initialize(settings = {})
20
- settings = defaults.merge(settings)
21
- settings.each do |property, value|
22
- next unless defaults.key?(property)
20
+ DEFAULTS.merge(settings).each do |property, value|
21
+ next unless DEFAULTS.key?(property)
23
22
 
24
- instance_variable_set(:"@#{property}", value)
25
- self.class.send(:attr_accessor, property)
23
+ send(:"#{property}=", value)
26
24
  end
27
25
  end
28
26
  end
@@ -33,6 +33,7 @@ module ArchivesSpace
33
33
  loop do
34
34
  options[:query] ||= {}
35
35
  options[:query][:page] = page
36
+ options[:query][:page_size] ||= config.page_size
36
37
  result = get(path, options)
37
38
  results = []
38
39
 
@@ -3,44 +3,32 @@
3
3
  module ArchivesSpace
4
4
  class Request
5
5
  include HTTParty
6
+
6
7
  attr_reader :config, :headers, :method, :path, :options
7
8
 
8
- def default_headers(method = :get)
9
- headers = {
10
- delete: {},
11
- get: {},
12
- post: {
13
- "Content-Type" => "application/json",
14
- "Content-Length" => "nnnn"
15
- },
16
- put: {
17
- "Content-Type" => "application/json",
18
- "Content-Length" => "nnnn"
19
- }
20
- }
21
- headers[method]
22
- end
9
+ DEFAULT_HEADERS = {"Content-Type" => "application/json"}.freeze
23
10
 
24
- def initialize(config, method = "GET", path = "", options = {})
11
+ def initialize(context, config, method = "GET", path = "", options = {})
25
12
  @config = config
13
+
26
14
  @method = method.downcase.to_sym
27
15
  @path = path.gsub(%r{^/+}, "")
16
+
28
17
  @options = options
29
- @options[:headers] =
30
- options[:headers] ? default_headers(@method).merge(options[:headers]) : default_headers(@method)
18
+
19
+ @options[:headers] = DEFAULT_HEADERS.merge(@options.fetch(:headers, {}))
31
20
  @options[:headers]["User-Agent"] = "#{Client::NAME}/#{Client::VERSION}"
21
+
32
22
  @options[:verify] = config.verify_ssl
33
23
  @options[:timeout] = config.timeout
34
24
  @options[:query] = {} unless options.key? :query
35
25
 
36
26
  self.class.debug_output($stdout) if @config.debug
37
-
38
- base_uri = config.base_repo&.length&.positive? ? File.join(config.base_uri, config.base_repo) : config.base_uri
39
- self.class.base_uri base_uri
27
+ self.class.base_uri context ? "#{config.base_uri}/#{context}" : config.base_uri
40
28
  end
41
29
 
42
30
  def execute
43
- self.class.send method, "/#{path}", options
31
+ Response.new(self.class.send(method, "/#{path}", options))
44
32
  end
45
33
  end
46
34
  end
@@ -34,21 +34,25 @@ module ArchivesSpace
34
34
  def login
35
35
  username = config.username
36
36
  password = config.password
37
- base_repo = config.base_repo
38
- use_global_repository # ensure we're in the global scope to login
39
- result = request("POST", "/users/#{username}/login", {query: {password: password}})
40
- unless result.parsed["session"]
41
- raise ConnectionError, "API client login failed as user [#{username}], check username and password are correct"
42
- end
43
37
 
44
- config.base_repo = base_repo # reset repo as set by the cfg
45
- @token = result.parsed["session"]
46
- self
38
+ previous_context = @context
39
+ @context = nil
40
+ begin
41
+ result = request("POST", "/users/#{username}/login", {query: {password: password}})
42
+ unless result.parsed["session"]
43
+ raise AuthenticationError, "Login failed as user [#{username}] (status #{result.status_code}); check username and password"
44
+ end
45
+
46
+ @token = result.parsed["session"]
47
+ self
48
+ ensure
49
+ @context = previous_context
50
+ end
47
51
  end
48
52
 
49
53
  def password_reset(username, password)
50
54
  user = all("users").find { |u| u["username"] == username }
51
- raise RequestError, user.status unless user
55
+ raise RequestError, "User not found: #{username}" unless user
52
56
 
53
57
  post(user["uri"], user, {password: password})
54
58
  end
@@ -7,7 +7,7 @@ module ArchivesSpace
7
7
  end
8
8
 
9
9
  def self.process(template, data)
10
- processor = File.extname(template).delete(".").camelize
10
+ processor = File.extname(template).delete(".").capitalize
11
11
  processor = Object.const_get("ArchivesSpace::Template::#{processor}")
12
12
  processor.new(template, data).process
13
13
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ArchivesSpace
4
4
  class Client
5
- VERSION = "0.4.2"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -3,7 +3,6 @@
3
3
  require "dry/cli"
4
4
  require "httparty"
5
5
  require "json"
6
- require "nokogiri"
7
6
  require "jbuilder"
8
7
 
9
8
  # mixins required first
@@ -23,13 +22,11 @@ require "archivesspace/client/cli/version"
23
22
  require "archivesspace/client/cli" # load the registry last
24
23
 
25
24
  module ArchivesSpace
26
- class ConnectionError < RuntimeError; end
25
+ class AuthenticationError < StandardError; end
27
26
 
28
- class ContextError < RuntimeError; end
27
+ class ConfigurationError < StandardError; end
29
28
 
30
- class RepositoryIdError < RuntimeError; end
29
+ class RepositoryIdError < StandardError; end
31
30
 
32
- class ParamsError < RuntimeError; end
33
-
34
- class RequestError < RuntimeError; end
31
+ class RequestError < StandardError; end
35
32
  end
@@ -11,7 +11,7 @@ describe ArchivesSpace::Client do
11
11
  end
12
12
 
13
13
  it "will raise an error if supplied configuration is of invalid type" do
14
- expect { ArchivesSpace::Client.new({base_uri: CUSTOM_BASE_URI}) }.to raise_error(RuntimeError)
14
+ expect { ArchivesSpace::Client.new({base_uri: CUSTOM_BASE_URI}) }.to raise_error(ArchivesSpace::ConfigurationError)
15
15
  end
16
16
 
17
17
  it "will allow a configuration object to be provided" do
@@ -20,15 +20,86 @@ describe ArchivesSpace::Client do
20
20
  end
21
21
  end
22
22
 
23
+ describe "Login" do
24
+ it "sends the login request at global scope even when a repo context is set" do
25
+ client.repository 2
26
+ expect(ArchivesSpace::Request).to receive(:new)
27
+ .with(nil, client.config, "POST", "/users/admin/login", anything)
28
+ .and_wrap_original do |_orig, *_args|
29
+ double("request", execute: double("response", parsed: {"session" => "token"}, status_code: 200))
30
+ end
31
+ client.login
32
+ end
33
+
34
+ it "restores the previous repo context after login" do
35
+ client.repository 2
36
+ allow(ArchivesSpace::Request).to receive(:new).and_return(
37
+ double("request", execute: double("response", parsed: {"session" => "token"}, status_code: 200))
38
+ )
39
+ client.login
40
+ expect(client.context).to eq "repositories/2"
41
+ end
42
+
43
+ it "raises AuthenticationError and restores context when credentials are rejected" do
44
+ client.repository 2
45
+ allow(ArchivesSpace::Request).to receive(:new).and_return(
46
+ double("request", execute: double("response", parsed: {}, status_code: 403))
47
+ )
48
+ expect { client.login }.to raise_error(ArchivesSpace::AuthenticationError, /status 403/)
49
+ expect(client.context).to eq "repositories/2"
50
+ end
51
+
52
+ it "raises AuthenticationError for a real 403 response (VCR)" do
53
+ bad_client = ArchivesSpace::Client.new(
54
+ ArchivesSpace::Configuration.new(password: "wrong")
55
+ )
56
+ VCR.use_cassette("login_failure") do
57
+ expect { bad_client.login }.to raise_error(
58
+ ArchivesSpace::AuthenticationError, /admin.*status 403/
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "Pagination" do
65
+ it "will have a method for defined paginated record types" do
66
+ ArchivesSpace::Pagination::ENDPOINTS.each do |e|
67
+ next if e.match?("/")
68
+
69
+ expect(client.respond_to?(e.to_sym)).to be true
70
+ end
71
+ end
72
+
73
+ it "will have a method for defined paginated record types with multipart path" do
74
+ expect(client.respond_to?(:people)).to be true
75
+ end
76
+
77
+ it "will pass page_size from configuration to the query" do
78
+ response = double("response", parsed: {"results" => []})
79
+ allow(client).to receive(:get).and_return(response)
80
+ client.all("resources").first
81
+ expect(client).to have_received(:get).with("resources", hash_including(query: hash_including(page_size: 50)))
82
+ end
83
+ end
84
+
85
+ describe "Password reset" do
86
+ it "will raise an error if the user is not found" do
87
+ allow(client).to receive(:all).with("users").and_return([].lazy)
88
+ expect { client.password_reset("nonexistent", "newpass") }.to raise_error(
89
+ ArchivesSpace::RequestError, "User not found: nonexistent"
90
+ )
91
+ end
92
+ end
93
+
23
94
  describe "Repository scoping" do
24
- it "will set the repository with an integer id" do
95
+ it "will set the context with an integer id" do
25
96
  client.repository 2
26
- expect(client.config.base_repo).to eq "repositories/2"
97
+ expect(client.context).to eq "repositories/2"
27
98
  end
28
99
 
29
- it "will set the repository with a string id cast to integer" do
100
+ it "will set the context with a string id cast to integer" do
30
101
  client.repository "2"
31
- expect(client.config.base_repo).to eq "repositories/2"
102
+ expect(client.context).to eq "repositories/2"
32
103
  end
33
104
 
34
105
  it "will fail if the id cannot be cast to integer" do
@@ -37,37 +108,81 @@ describe ArchivesSpace::Client do
37
108
  )
38
109
  end
39
110
 
40
- it "will use the global repo if repository is passed nil" do
111
+ it "will fail if the id is not a valid type" do
112
+ expect { client.repository([]) }.to raise_error(
113
+ ArchivesSpace::RepositoryIdError
114
+ )
115
+ end
116
+
117
+ it "will clear the context when passed nil" do
41
118
  client.repository 2
42
119
  client.repository nil
43
- expect(client.config.base_repo).to eq ""
120
+ expect(client.context).to be_nil
44
121
  end
45
122
 
46
- it "will use the global repo when the method is called" do
123
+ it "will clear the context when use_global_repository is called" do
47
124
  client.repository 2
48
125
  client.use_global_repository
49
- expect(client.config.base_repo).to eq ""
126
+ expect(client.context).to be_nil
127
+ end
128
+
129
+ it "scopes the context to a block and restores afterwards" do
130
+ client.repository(2) do
131
+ expect(client.context).to eq "repositories/2"
132
+ end
133
+ expect(client.context).to be_nil
134
+ end
135
+
136
+ it "restores the previous context when nested" do
137
+ client.repository 2
138
+ client.repository(3) do
139
+ expect(client.context).to eq "repositories/3"
140
+ end
141
+ expect(client.context).to eq "repositories/2"
142
+ end
143
+
144
+ it "restores the context even if the block raises" do
145
+ expect { client.repository(2) { raise "boom" } }.to raise_error("boom")
146
+ expect(client.context).to be_nil
147
+ end
148
+
149
+ it "does not rebrand ArgumentError raised from within the block" do
150
+ expect {
151
+ client.repository(2) { raise ArgumentError, "from caller" }
152
+ }.to raise_error(ArgumentError, "from caller")
50
153
  end
51
154
  end
52
155
 
53
156
  describe "Requests" do
54
157
  it "will have an identifiable user agent" do
55
- request = ArchivesSpace::Request.new(client.config)
158
+ request = ArchivesSpace::Request.new(nil, client.config)
56
159
  expect(request.options[:headers]["User-Agent"]).to eq "#{ArchivesSpace::Client::NAME}/#{ArchivesSpace::Client::VERSION}"
57
160
  end
58
161
  end
59
162
 
60
- describe "Pagination" do
61
- it "will have a method for defined paginated record types" do
62
- ArchivesSpace::Pagination::ENDPOINTS.each do |e|
63
- next if e.match?("/")
163
+ describe "URL construction" do
164
+ let(:config) { ArchivesSpace::Configuration.new(base_uri: "http://localhost:8089") }
64
165
 
65
- expect(client.respond_to?(e.to_sym)).to be true
66
- end
166
+ it "uses the bare base_uri when context is nil" do
167
+ ArchivesSpace::Request.new(nil, config, "GET", "repositories")
168
+ expect(ArchivesSpace::Request.base_uri).to eq "http://localhost:8089"
67
169
  end
68
170
 
69
- it "will have a method for defined paginated record types with multipart path" do
70
- expect(client.respond_to?(:people)).to be true
171
+ it "appends a repository context to the base_uri" do
172
+ ArchivesSpace::Request.new("repositories/2", config, "GET", "resources")
173
+ expect(ArchivesSpace::Request.base_uri).to eq "http://localhost:8089/repositories/2"
174
+ end
175
+
176
+ it "preserves a path prefix in base_uri when context is nil" do
177
+ config.base_uri = "https://example.org/staff/api"
178
+ ArchivesSpace::Request.new(nil, config, "GET", "repositories")
179
+ expect(ArchivesSpace::Request.base_uri).to eq "https://example.org/staff/api"
180
+ end
181
+
182
+ it "preserves a path prefix in base_uri when scoped to a repository" do
183
+ config.base_uri = "https://example.org/staff/api"
184
+ ArchivesSpace::Request.new("repositories/2", config, "GET", "resources")
185
+ expect(ArchivesSpace::Request.base_uri).to eq "https://example.org/staff/api/repositories/2"
71
186
  end
72
187
  end
73
188
 
@@ -0,0 +1,37 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://localhost:8089/users/admin/login?password=wrong
6
+ body:
7
+ encoding: UTF-8
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ User-Agent:
13
+ - ArchivesSpaceClient/0.3.0
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 403
21
+ message: Forbidden
22
+ headers:
23
+ Cache-Control:
24
+ - private, must-revalidate, max-age=0
25
+ Content-Type:
26
+ - application/json
27
+ X-Content-Type-Options:
28
+ - nosniff
29
+ Content-Length:
30
+ - '24'
31
+ Server:
32
+ - Jetty(9.4.44.v20210927)
33
+ body:
34
+ encoding: UTF-8
35
+ string: '{"error":"Login failed"}'
36
+ recorded_at: Tue, 22 Apr 2026 12:00:00 GMT
37
+ recorded_with: VCR 6.3.1
data/spec/spec_helper.rb CHANGED
@@ -6,8 +6,8 @@ require "vcr"
6
6
  require "webmock/rspec"
7
7
 
8
8
  # GLOBAL VALUES FOR SPECS
9
- DEFAULT_BASE_URI = "http://localhost:8089"
10
9
  CUSTOM_BASE_URI = "https://archives.university.edu/api"
10
+ DEFAULT_BASE_URI = "http://localhost:8089"
11
11
 
12
12
  VCR.configure do |c|
13
13
  c.cassette_library_dir = "spec/fixtures/cassettes"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: archivesspace-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Cooper
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-14 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: aruba
@@ -75,72 +75,72 @@ dependencies:
75
75
  name: rspec
76
76
  requirement: !ruby/object:Gem::Requirement
77
77
  requirements:
78
- - - '='
78
+ - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: 3.6.0
80
+ version: '3.13'
81
81
  type: :development
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
- - - '='
85
+ - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: 3.6.0
87
+ version: '3.13'
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: rubocop
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
- - - '='
92
+ - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '1.56'
94
+ version: '1.72'
95
95
  type: :development
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
- - - '='
99
+ - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: '1.56'
101
+ version: '1.72'
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: standard
104
104
  requirement: !ruby/object:Gem::Requirement
105
105
  requirements:
106
- - - '='
106
+ - - "~>"
107
107
  - !ruby/object:Gem::Version
108
- version: 1.31.0
108
+ version: '1.44'
109
109
  type: :development
110
110
  prerelease: false
111
111
  version_requirements: !ruby/object:Gem::Requirement
112
112
  requirements:
113
- - - '='
113
+ - - "~>"
114
114
  - !ruby/object:Gem::Version
115
- version: 1.31.0
115
+ version: '1.44'
116
116
  - !ruby/object:Gem::Dependency
117
117
  name: vcr
118
118
  requirement: !ruby/object:Gem::Requirement
119
119
  requirements:
120
- - - '='
120
+ - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: 6.2.0
122
+ version: '6.3'
123
123
  type: :development
124
124
  prerelease: false
125
125
  version_requirements: !ruby/object:Gem::Requirement
126
126
  requirements:
127
- - - '='
127
+ - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: 6.2.0
129
+ version: '6.3'
130
130
  - !ruby/object:Gem::Dependency
131
131
  name: webmock
132
132
  requirement: !ruby/object:Gem::Requirement
133
133
  requirements:
134
- - - '='
134
+ - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: 3.19.1
136
+ version: '3.24'
137
137
  type: :development
138
138
  prerelease: false
139
139
  version_requirements: !ruby/object:Gem::Requirement
140
140
  requirements:
141
- - - '='
141
+ - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: 3.19.1
143
+ version: '3.24'
144
144
  - !ruby/object:Gem::Dependency
145
145
  name: dry-cli
146
146
  requirement: !ruby/object:Gem::Requirement
@@ -183,20 +183,6 @@ dependencies:
183
183
  - - "~>"
184
184
  - !ruby/object:Gem::Version
185
185
  version: '2.0'
186
- - !ruby/object:Gem::Dependency
187
- name: nokogiri
188
- requirement: !ruby/object:Gem::Requirement
189
- requirements:
190
- - - "~>"
191
- - !ruby/object:Gem::Version
192
- version: '1.10'
193
- type: :runtime
194
- prerelease: false
195
- version_requirements: !ruby/object:Gem::Requirement
196
- requirements:
197
- - - "~>"
198
- - !ruby/object:Gem::Version
199
- version: '1.10'
200
186
  - !ruby/object:Gem::Dependency
201
187
  name: jbuilder
202
188
  requirement: !ruby/object:Gem::Requirement
@@ -236,6 +222,7 @@ files:
236
222
  - examples/repo_and_user.rb
237
223
  - examples/templates.rb
238
224
  - examples/test_connection.rb
225
+ - examples/update_feed.rb
239
226
  - examples/user_groups.rb
240
227
  - exe/asclient
241
228
  - features/exec.feature
@@ -262,6 +249,7 @@ files:
262
249
  - spec/archivesspace/configuration_spec.rb
263
250
  - spec/archivesspace/templates_spec.rb
264
251
  - spec/fixtures/cassettes/backend_version.yml
252
+ - spec/fixtures/cassettes/login_failure.yml
265
253
  - spec/spec_helper.rb
266
254
  homepage: ''
267
255
  licenses:
@@ -281,7 +269,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
281
269
  - !ruby/object:Gem::Version
282
270
  version: '0'
283
271
  requirements: []
284
- rubygems_version: 3.6.3
272
+ rubygems_version: 4.0.10
285
273
  specification_version: 4
286
274
  summary: Interact with ArchivesSpace via the API.
287
275
  test_files: []