Skip to content

Commit 299ef0c

Browse files
anarchivistawilfox
andauthored
DEV-1119: refactor healthchecks (#19)
* DEV-1119: refactor healthchecks splits the generalized new healthcheck introduced to fix this issue into its own class; IIIF item checks should happen separately, even if they rely on similar logic * Apply suggestions from code review Co-authored-by: Anna Wilcox <AWilcox@Wilcox-Tech.com> --------- Co-authored-by: Anna Wilcox <AWilcox@Wilcox-Tech.com>
1 parent 7873328 commit 299ef0c

File tree

8 files changed

+391
-248
lines changed

8 files changed

+391
-248
lines changed

app/lib/health_checks.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
module HealthChecks
44
CHECK_FILES = %w[
5+
iiif_item_check
56
iiif_server_check
67
lending_root_path
78
test_item_exists
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require 'health_checks/iiif_server_check'
2+
3+
module HealthChecks
4+
class IIIFItemCheck < IIIFServerCheck
5+
6+
private
7+
8+
def iiif_test_uri
9+
@iiif_test_uri ||= begin
10+
item = Item.active.first || Item.inactive.first
11+
if item && iiif_base_uri
12+
BerkeleyLibrary::Util::URIs.append(
13+
iiif_base_uri,
14+
item.iiif_directory.first_image_url_path,
15+
'info.json'
16+
)
17+
end
18+
end
19+
end
20+
21+
def perform_request
22+
# iipsrv won't return an CORS header for the "health" endpoint, so
23+
# we still call a test item to do a separate check
24+
response = super
25+
return response if response[:failure]
26+
27+
acao_header = response[:rsp].headers['Access-Control-Allow-Origin']
28+
if acao_header.blank?
29+
response.merge!({
30+
message: "GET #{iiif_test_uri} missing Access-Control-Allow-Origin header", failure: true
31+
})
32+
end
33+
response
34+
end
35+
end
36+
end

app/lib/health_checks/iiif_server_check.rb

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ class IIIFServerCheck < OkComputer::Check
33
include BerkeleyLibrary::Logging
44

55
def check
6-
result = validate_iiif_server
6+
result = perform_request
77
mark_message result[:message]
88
mark_failure if result[:failure]
99
rescue StandardError => e
@@ -21,32 +21,22 @@ def iiif_connection
2121
end
2222
end
2323

24-
def iiif_test_uri
25-
base_uri = Lending::Config.iiif_base_uri
26-
return unless base_uri
27-
28-
item = Item.active.first || Item.inactive.first
29-
return unless item
30-
31-
BerkeleyLibrary::Util::URIs.append(
32-
base_uri,
33-
item.iiif_directory.first_image_url_path,
34-
'info.json'
35-
)
24+
def iiif_base_uri
25+
@iiif_base_uri ||= Lending::Config.iiif_base_uri
3626
end
3727

38-
# Returns a hash with :message and :failure keys
39-
def validate_iiif_server
40-
test_uri = iiif_test_uri
41-
return { message: 'Unable to construct test image URI', failure: true } unless test_uri
28+
def iiif_test_uri
29+
@iiif_test_uri ||= (URI.join(iiif_base_uri, '/health') if iiif_base_uri)
30+
end
4231

43-
response = iiif_connection.head(test_uri)
44-
return { message: "HEAD #{test_uri} returned status #{response.status}", failure: true } unless response.success?
32+
# Returns a hash with :message, :failure, and :rsp keys
33+
def perform_request
34+
return { message: 'Unable to construct healthcheck URI', failure: true, rsp: nil } unless iiif_test_uri
4535

46-
acao_header = response.headers['Access-Control-Allow-Origin']
47-
return { message: "HEAD #{test_uri} missing Access-Control-Allow-Origin header", failure: true } if acao_header.blank?
36+
response = iiif_connection.get(iiif_test_uri)
37+
return { message: "GET #{iiif_test_uri} returned status #{response.status}", failure: true, rsp: response } unless response.success?
4838

49-
{ message: 'IIIF server reachable', failure: false }
39+
{ message: 'HTTP check successful', failure: false, rsp: response }
5040
end
5141
end
5242
end

config/initializers/okcomputer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
# Check that DB migrations have run
1010
OkComputer::Registry.register 'database-migrations', OkComputer::ActiveRecordMigrationsCheck.new
1111

12-
# Custom IIIF server check
12+
# Custom IIIF server checks
1313
OkComputer::Registry.register 'iiif-server', HealthChecks::IIIFServerCheck.new
14+
OkComputer::Registry.register 'iiif-item', HealthChecks::IIIFItemCheck.new
1415

1516
# TODO: Custom Test Item Exists
1617
OkComputer::Registry.register 'test-item-exists', HealthChecks::TestItemExists.new
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
require 'rails_helper'
2+
require 'support/iiif_check_helper'
3+
4+
RSpec.describe HealthChecks::IIIFItemCheck do
5+
subject(:check) { described_class.new }
6+
7+
def run_check
8+
check.run
9+
check
10+
end
11+
12+
describe '#check' do
13+
14+
context 'with a valid IIIF base URI' do
15+
include_context 'IIIF item checks'
16+
17+
it 'fails and sets message when the GET health check response is not successful' do
18+
response = instance_double('Faraday::Response',
19+
success?: false,
20+
status: 503,
21+
headers: {})
22+
23+
allow(connection).to receive(:get).with(test_uri).and_return(response)
24+
25+
run_check
26+
27+
expect(check.message).to match(/returned status 503/)
28+
29+
if check.respond_to?(:failure?)
30+
expect(check.failure?).to be(true)
31+
else
32+
expect(check.instance_variable_get(:@failure_occurred)).to be(true)
33+
end
34+
end
35+
36+
it 'fails and sets message when Access-Control-Allow-Origin header is missing/blank' do
37+
response = instance_double('Faraday::Response',
38+
success?: true,
39+
status: 200,
40+
headers: { 'Access-Control-Allow-Origin' => '' })
41+
42+
allow(connection).to receive(:get).with(test_uri).and_return(response)
43+
44+
run_check
45+
46+
expect(check.message).to eq(
47+
"GET #{test_uri} missing Access-Control-Allow-Origin header"
48+
)
49+
50+
if check.respond_to?(:failure?)
51+
expect(check.failure?).to be(true)
52+
else
53+
expect(check.instance_variable_get(:@failure_occurred)).to be(true)
54+
end
55+
end
56+
57+
it 'does not fail when reachable and ACAO header present' do
58+
response = instance_double('Faraday::Response',
59+
success?: true,
60+
status: 200,
61+
headers: { 'Access-Control-Allow-Origin' => '*' })
62+
63+
allow(connection).to receive(:get).with(test_uri).and_return(response)
64+
65+
run_check
66+
67+
expect(check.message).to eq('HTTP check successful')
68+
69+
if check.respond_to?(:failure?)
70+
expect(check.failure?).to be(false)
71+
else
72+
expect(check.instance_variable_get(:@failure_occurred)).not_to be(true)
73+
end
74+
end
75+
76+
it 'fails and sets message when an exception is raised' do
77+
allow(connection).to receive(:get).with(test_uri).and_raise(StandardError, 'boom')
78+
79+
run_check
80+
81+
expect(check.message).to match('StandardError')
82+
83+
if check.respond_to?(:failure?)
84+
expect(check.failure?).to be(true)
85+
else
86+
expect(check.instance_variable_get(:@failure_occurred)).to be(true)
87+
end
88+
end
89+
end
90+
end
91+
92+
describe 'private helpers' do
93+
describe '#iiif_connection' do
94+
it 'configures Faraday timeouts' do
95+
conn = check.send(:iiif_connection)
96+
97+
expect(conn.options.open_timeout).to eq(2)
98+
expect(conn.options.timeout).to eq(3)
99+
end
100+
end
101+
102+
describe '#iiif_base_uri' do
103+
it 'returns nil when iiif_base_uri is nil' do
104+
allow(Lending::Config).to receive(:iiif_base_uri).and_return(nil)
105+
106+
expect(check.send(:iiif_base_uri)).to be_nil
107+
end
108+
109+
it 'returns the Lending::Config value when set' do
110+
allow(Lending::Config).to receive(:iiif_base_uri).and_return('http://example.test/iiif')
111+
expect(check.send(:iiif_base_uri)).to eq('http://example.test/iiif')
112+
end
113+
end
114+
115+
describe '#iiif_test_uri' do
116+
it 'returns nil when iiif_base_uri is nil' do
117+
allow(Lending::Config).to receive(:iiif_base_uri).and_return(nil)
118+
119+
expect(check.send(:iiif_test_uri)).to be_nil
120+
end
121+
122+
context 'with IIIF item checks' do
123+
include_context 'IIIF item checks'
124+
125+
it 'returns nil when no Item exists' do
126+
allow(Lending::Config).to receive(:iiif_base_uri).and_return(base_uri)
127+
stub_items(active_first: nil, inactive_first: nil)
128+
129+
expect(check.send(:iiif_test_uri)).to be_nil
130+
end
131+
132+
it 'builds a test uri from an active item (or inactive fallback)' do
133+
base_uri = URI('http://example.test/iiif/')
134+
allow(Lending::Config).to receive(:iiif_base_uri).and_return(base_uri)
135+
136+
iiif_dir = instance_double('IiifDirectory', first_image_url_path: 'some/path')
137+
item = instance_double('Item', iiif_directory: iiif_dir)
138+
stub_items(active_first: item, inactive_first: nil)
139+
140+
expect(BerkeleyLibrary::Util::URIs).to receive(:append)
141+
.with(base_uri, 'some/path', 'info.json')
142+
.and_return('http://example.test/iiif/some/path/info.json')
143+
144+
expect(check.send(:iiif_test_uri)).to eq('http://example.test/iiif/some/path/info.json')
145+
end
146+
end
147+
end
148+
149+
describe '#perform_request' do
150+
it 'returns a failure when it cannot construct test uri' do
151+
allow(Lending::Config).to receive(:iiif_base_uri).and_return(nil)
152+
153+
result = check.send(:perform_request)
154+
155+
expect(result).to eq(message: 'Unable to construct healthcheck URI', failure: true, rsp: nil)
156+
end
157+
158+
context 'with IIIF item checks' do
159+
include_context 'IIIF item checks'
160+
it 'returns a failure when the GET response is not successful' do
161+
response = instance_double('Faraday::Response',
162+
success?: false,
163+
status: 503,
164+
headers: {})
165+
166+
allow(connection).to receive(:get).with(test_uri).and_return(response)
167+
168+
result = check.send(:perform_request)
169+
170+
expect(result[:failure]).to be(true)
171+
expect(result[:message]).to match(/returned status 503/)
172+
end
173+
174+
it 'returns a failure when Access-Control-Allow-Origin header is missing/blank' do
175+
response = instance_double('Faraday::Response',
176+
success?: true,
177+
status: 200,
178+
headers: { 'Access-Control-Allow-Origin' => '' })
179+
180+
allow(connection).to receive(:get).with(test_uri).and_return(response)
181+
182+
result = check.send(:perform_request)
183+
184+
expect(result).to eq(
185+
message: "GET #{test_uri} missing Access-Control-Allow-Origin header",
186+
failure: true,
187+
rsp: response
188+
)
189+
end
190+
191+
it 'returns ok when reachable and ACAO header present' do
192+
response = instance_double('Faraday::Response',
193+
success?: true,
194+
status: 200,
195+
headers: { 'Access-Control-Allow-Origin' => '*' })
196+
197+
allow(connection).to receive(:get).with(test_uri).and_return(response)
198+
199+
result = check.send(:perform_request)
200+
201+
expect(result).to eq(message: 'HTTP check successful', failure: false, rsp: response)
202+
end
203+
end
204+
end
205+
end
206+
end

0 commit comments

Comments
 (0)