Skip to content

Commit 14b121c

Browse files
authored
Merge pull request #248 from sul-dlss/delete-and-extensions#244
2 parents 665bae0 + bdec3ff commit 14b121c

5 files changed

Lines changed: 180 additions & 8 deletions

File tree

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ RSpec/MultipleExpectations:
2222
Max: 3
2323
RSpec/ExampleLength:
2424
Max: 15
25+
RSpec/NestedGroups:
26+
Enabled: false
2527

2628
RSpec/BeEq: # new in 2.9.0
2729
Enabled: true

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
1818

1919
## Usage
2020

21-
The gem should be configured first, and then you can either call API endpoints directly using GET or POST, or more commonly, use the helper methods provided, as described in the section below.
21+
The gem should be configured first, and then you can either call API endpoints directly using GET, POST, PUT, and DELETE. It may be more convenient to use the helper methods provided, as described in the section below, if your use case is already covered by what's been implemented already.
2222

2323
```ruby
2424
require 'folio_client'
@@ -34,6 +34,12 @@ client = FolioClient.configure(
3434
response = client.get('/organizations/organizations', {query_string_param: 'abcdef'})
3535

3636
response = client.post('/some/post/endpoint', params_hash.to_json)
37+
38+
# If you want direct access to the response object for your own handling, you can also
39+
# pass a block to the get, post, put, and delete methods:
40+
response = client.post('/some/post/endpoint', params_hash.to_json) do |resp|
41+
# Do something with resp.status, resp.headers, resp.body, etc.
42+
end
3743
```
3844

3945
Note that the settings will live in the consumer of this gem and would typically be used like this:

lib/folio_client.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def configure(url:, login_params:, tenant_id: nil, user_agent: nil, timeout: nil
103103
# @param path [String] API path relative to configured +url+
104104
# @param params [Hash] query parameters
105105
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
106+
# @yield [Faraday::Response] optional block to receive the raw +Faraday::Response+ object
106107
# @raise [FolioClient::Error] when Folio responds with an unexpected status
107108
def get(path, params = {})
108109
response = with_token_refresh_when_unauthorized do
@@ -111,6 +112,8 @@ def get(path, params = {})
111112

112113
UnexpectedResponse.call(response) unless response.success?
113114

115+
yield response if block_given?
116+
114117
JSON.parse(response.body) if response.body.present?
115118
end
116119

@@ -121,6 +124,7 @@ def get(path, params = {})
121124
# @param body [Hash, String, nil] request payload
122125
# @param content_type [String] MIME type of request body
123126
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
127+
# @yield [Faraday::Response] optional block to receive the raw +Faraday::Response+ object
124128
# @raise [FolioClient::Error] when Folio responds with an unexpected status
125129
def post(path, body = nil, content_type: 'application/json')
126130
req_body = content_type == 'application/json' ? body&.to_json : body
@@ -130,6 +134,8 @@ def post(path, body = nil, content_type: 'application/json')
130134

131135
UnexpectedResponse.call(response) unless response.success?
132136

137+
yield response if block_given?
138+
133139
JSON.parse(response.body) if response.body.present?
134140
end
135141

@@ -141,6 +147,7 @@ def post(path, body = nil, content_type: 'application/json')
141147
# @param content_type [String] MIME type of request body
142148
# @param exception_args [Hash] supplemental context forwarded to +UnexpectedResponse+
143149
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
150+
# @yield [Faraday::Response] optional block to receive the raw +Faraday::Response+ object
144151
# @raise [FolioClient::Error] when Folio responds with an unexpected status
145152
def put(path, body = nil, content_type: 'application/json', **exception_args)
146153
req_body = content_type == 'application/json' ? body&.to_json : body
@@ -150,6 +157,27 @@ def put(path, body = nil, content_type: 'application/json', **exception_args)
150157

151158
UnexpectedResponse.call(response, **exception_args) unless response.success?
152159

160+
yield response if block_given?
161+
162+
JSON.parse(response.body) if response.body.present?
163+
end
164+
165+
# Send an authenticated DELETE request
166+
# @note None of the current FolioClient services use this method, but it's provided
167+
# primarily to accommodate work in folio-tasks
168+
# @param path [String] API path relative to configured +url+
169+
# @return [Hash, Array, nil] parsed JSON body, or +nil+ for empty body
170+
# @yield [Faraday::Response] optional block to receive the raw +Faraday::Response+ object
171+
# @raise [FolioClient::Error] when Folio responds with an unexpected status
172+
def delete(path)
173+
response = with_token_refresh_when_unauthorized do
174+
connection.delete(path)
175+
end
176+
177+
UnexpectedResponse.call(response) unless response.success?
178+
179+
yield response if block_given?
180+
153181
JSON.parse(response.body) if response.body.present?
154182
end
155183

spec/folio_client_spec.rb

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272

7373
describe '#get' do
7474
let(:path) { 'some_path' }
75-
let(:response) { { some: 'response' }.to_json }
75+
let(:response) { { 'some' => 'response' } }
7676

7777
before do
7878
stub_request(:get, "#{url}/#{path}?id=5")
@@ -82,11 +82,20 @@
8282
it 'calls the API with a get' do
8383
expect(client.get(path, { id: 5 })).to eq(response)
8484
end
85+
86+
context 'when block is passed' do
87+
it 'calls the API with a get and yields the response' do
88+
client.get(path, { id: 5 }) do |resp|
89+
expect(resp).to be_a(Faraday::Response)
90+
expect(resp.body).to eq(response.to_json)
91+
end
92+
end
93+
end
8594
end
8695

8796
describe '#post' do
8897
let(:path) { 'some_path' }
89-
let(:response) { { some: 'response' }.to_json }
98+
let(:response) { { 'some' => 'response' } }
9099

91100
context 'with a JSON body' do
92101
before do
@@ -104,6 +113,15 @@
104113
it 'calls the API with a post' do
105114
expect(client.post(path, { id: 5 })).to eq(response)
106115
end
116+
117+
context 'when block is passed' do
118+
it 'calls the API with a post and yields the response' do
119+
client.post(path, { id: 5 }) do |resp|
120+
expect(resp).to be_a(Faraday::Response)
121+
expect(resp.body).to eq(response.to_json)
122+
end
123+
end
124+
end
107125
end
108126

109127
context 'with no body' do
@@ -144,7 +162,7 @@
144162

145163
describe '#put' do
146164
let(:path) { 'some_path' }
147-
let(:response) { { some: 'response' }.to_json }
165+
let(:response) { { 'some' => 'response' } }
148166

149167
context 'with a JSON body' do
150168
before do
@@ -162,6 +180,15 @@
162180
it 'calls the API with a put' do
163181
expect(client.put(path, { id: 5 })).to eq(response)
164182
end
183+
184+
context 'when block is passed' do
185+
it 'calls the API with a put and yields the response' do
186+
client.put(path, { id: 5 }) do |resp|
187+
expect(resp).to be_a(Faraday::Response)
188+
expect(resp.body).to eq(response.to_json)
189+
end
190+
end
191+
end
165192
end
166193

167194
context 'with no body' do
@@ -200,6 +227,29 @@
200227
end
201228
end
202229

230+
describe '#delete' do
231+
let(:path) { 'some_path' }
232+
let(:response) { { 'some' => 'response' } }
233+
234+
before do
235+
stub_request(:delete, "#{url}/#{path}")
236+
.to_return(status: 200, body: response.to_json)
237+
end
238+
239+
it 'calls the API with a delete' do
240+
expect(client.delete(path)).to eq(response)
241+
end
242+
243+
context 'when block is passed' do
244+
it 'calls the API with a delete and yields the response' do
245+
client.delete(path) do |resp|
246+
expect(resp).to be_a(Faraday::Response)
247+
expect(resp.body).to eq(response.to_json)
248+
end
249+
end
250+
end
251+
end
252+
203253
describe '.fetch_hrid' do
204254
let(:barcode) { '123456' }
205255

spec/spec_helper.rb

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,100 @@
2929
# require only the support files necessary.
3030
Dir[Pathname(Dir.pwd).join('spec/support/**/*.rb')].each { |f| require f }
3131

32+
# This file was generated by the `rspec --init` command. Conventionally, all
33+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
34+
# The generated `.rspec` file contains `--require spec_helper` which will cause
35+
# this file to always be loaded, without a need to explicitly require it in any
36+
# files.
37+
#
38+
# Given that it is always loaded, you are encouraged to keep this file as
39+
# light-weight as possible. Requiring heavyweight dependencies from this file
40+
# will add to the boot time of your test suite on EVERY test run, even for an
41+
# individual file that may not need all of that loaded. Instead, consider making
42+
# a separate helper file that requires the additional dependencies and performs
43+
# the additional setup, and require it from the spec files that actually need
44+
# it.
45+
#
46+
# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
3247
RSpec.configure do |config|
33-
# Enable flags like --only-failures and --next-failure
48+
# rspec-expectations config goes here. You can use an alternate
49+
# assertion/expectation library such as wrong or the stdlib/minitest
50+
# assertions if you prefer.
51+
config.expect_with :rspec do |expectations|
52+
# This option will default to `true` in RSpec 4. It makes the `description`
53+
# and `failure_message` of custom matchers include text for helper methods
54+
# defined using `chain`, e.g.:
55+
# be_bigger_than(2).and_smaller_than(4).description
56+
# # => "be bigger than 2 and smaller than 4"
57+
# ...rather than:
58+
# # => "be bigger than 2"
59+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
60+
end
61+
62+
# rspec-mocks config goes here. You can use an alternate test double
63+
# library (such as bogus or mocha) by changing the `mock_with` option here.
64+
config.mock_with :rspec do |mocks|
65+
# Prevents you from mocking or stubbing a method that does not exist on
66+
# a real object. This is generally recommended, and will default to
67+
# `true` in RSpec 4.
68+
mocks.verify_partial_doubles = true
69+
end
70+
71+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
72+
# have no way to turn it off -- the option exists only for backwards
73+
# compatibility in RSpec 3). It causes shared context metadata to be
74+
# inherited by the metadata hash of host groups and examples, rather than
75+
# triggering implicit auto-inclusion in groups with matching metadata.
76+
config.shared_context_metadata_behavior = :apply_to_host_groups
77+
78+
# The settings below are suggested to provide a good initial experience
79+
# with RSpec, but feel free to customize to your heart's content.
80+
81+
# This allows you to limit a spec run to individual examples or groups
82+
# you care about by tagging them with `:focus` metadata. When nothing
83+
# is tagged with `:focus`, all examples get run. RSpec also provides
84+
# aliases for `it`, `describe`, and `context` that include `:focus`
85+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
86+
config.filter_run_when_matching :focus
87+
88+
# Allows RSpec to persist some state between runs in order to support
89+
# the `--only-failures` and `--next-failure` CLI options. We recommend
90+
# you configure your source control system to ignore this file.
3491
config.example_status_persistence_file_path = '.rspec_status'
3592

36-
# Disable RSpec exposing methods globally on `Module` and `main`
93+
# Limits the available syntax to the non-monkey patched syntax that is
94+
# recommended. For more details, see:
95+
# https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/
3796
config.disable_monkey_patching!
3897

39-
config.expect_with :rspec do |c|
40-
c.syntax = :expect
98+
# This setting enables warnings. It's recommended, but in some cases may
99+
# be too noisy due to issues in dependencies.
100+
config.warnings = true
101+
102+
# Many RSpec users commonly either run the entire suite or an individual
103+
# file, and it's useful to allow more verbose output when running an
104+
# individual spec file.
105+
if config.files_to_run.one?
106+
# Use the documentation formatter for detailed output,
107+
# unless a formatter has already been configured
108+
# (e.g. via a command-line flag).
109+
config.default_formatter = 'doc'
41110
end
111+
112+
# Print the 10 slowest examples and example groups at the
113+
# end of the spec run, to help surface which specs are running
114+
# particularly slow.
115+
config.profile_examples = 10
116+
117+
# Run specs in random order to surface order dependencies. If you find an
118+
# order dependency and want to debug it, you can fix the order by providing
119+
# the seed, which is printed after each run.
120+
# --seed 1234
121+
config.order = :random
122+
123+
# Seed global randomization in this process using the `--seed` CLI option.
124+
# Setting this allows you to use `--seed` to deterministically reproduce
125+
# test failures related to randomization by passing the same `--seed` value
126+
# as the one that triggered the failure.
127+
Kernel.srand config.seed
42128
end

0 commit comments

Comments
 (0)