Skip to content

Commit e264e98

Browse files
committed
refactoring code and added tests
1 parent d204013 commit e264e98

File tree

2 files changed

+178
-75
lines changed

2 files changed

+178
-75
lines changed

app/services/registry_connector.rb

Lines changed: 51 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,89 +7,19 @@ class RegistryConnector
77
attr_accessor :request
88

99
def self.perform_request(request, url)
10-
response = Net::HTTP.start(url.host, url.port,
11-
use_ssl: url.scheme == 'https') do |http|
10+
response = Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http|
1211
http.request(request)
1312
end
1413

15-
body_as_string = response.body
16-
code_as_string = response.code.to_s
17-
18-
if [HTTP_CREATED, HTTP_SUCCESS].include?(code_as_string)
19-
JSON.parse(body_as_string)
20-
else
21-
raise CommunicationError.new(request, code_as_string)
22-
end
14+
handle_response(response, request)
2315
rescue Timeout::Error, SocketError, Errno::ECONNREFUSED => e
24-
logger.error({
25-
timestamp: Time.current.utc.iso8601(3),
26-
level: 'error',
27-
message: 'Registry API network connection failed',
28-
event: 'registry.api.network_error',
29-
service: 'rest-whois',
30-
environment: Rails.env,
31-
host: Socket.gethostname,
32-
pid: Process.pid,
33-
error: {
34-
type: e.class.name,
35-
message: e.message
36-
},
37-
details: {
38-
url: url.to_s,
39-
request_method: request.method,
40-
request_uri: request.uri
41-
},
42-
schema_version: '1.0.0',
43-
log_version: '1.0.0'
44-
}.to_json)
16+
log_error(e, request, url, 'network_error')
4517
false
4618
rescue CommunicationError => e
47-
logger.error({
48-
timestamp: Time.current.utc.iso8601(3),
49-
level: 'error',
50-
message: 'Registry API communication error',
51-
event: 'registry.api.communication_error',
52-
service: 'rest-whois',
53-
environment: Rails.env,
54-
host: Socket.gethostname,
55-
pid: Process.pid,
56-
error: {
57-
type: e.class.name,
58-
message: e.message
59-
},
60-
details: {
61-
url: url.to_s,
62-
request_method: request.method,
63-
request_uri: request.uri,
64-
response_body: response&.body
65-
},
66-
schema_version: '1.0.0',
67-
log_version: '1.0.0'
68-
}.to_json)
19+
log_error(e, request, url, 'communication_error', e.response)
6920
false
7021
rescue StandardError => e
71-
logger.error({
72-
timestamp: Time.current.utc.iso8601(3),
73-
level: 'error',
74-
message: 'Registry API unexpected error',
75-
event: 'registry.api.unexpected_error',
76-
service: 'rest-whois',
77-
environment: Rails.env,
78-
host: Socket.gethostname,
79-
pid: Process.pid,
80-
error: {
81-
type: e.class.name,
82-
message: e.message,
83-
stack: e.backtrace&.first(5)&.join(' | ')
84-
},
85-
details: {
86-
url: url.to_s,
87-
request_method: request.method,
88-
request_uri: request.uri
89-
},
90-
schema_version: '1.0.0',
91-
log_version: '1.0.0'
92-
}.to_json)
22+
log_error(e, request, url, 'unexpected_error')
9323
false
9424
end
9525

@@ -127,4 +57,50 @@ def self.request_by_type(type)
12757
def self.logger
12858
Rails.logger
12959
end
60+
61+
private_class_method
62+
63+
def self.handle_response(response, request)
64+
if [HTTP_CREATED, HTTP_SUCCESS].include?(response.code.to_s)
65+
JSON.parse(response.body)
66+
else
67+
raise CommunicationError.new(request, response)
68+
end
69+
end
70+
71+
def self.log_error(exception, request, url, event, response = nil)
72+
logger.error({
73+
timestamp: Time.current.utc.iso8601(3),
74+
level: 'error',
75+
message: "Registry API #{event.gsub('_', ' ')}",
76+
event: "registry.api.#{event}",
77+
service: 'rest-whois',
78+
environment: Rails.env,
79+
host: Socket.gethostname,
80+
pid: Process.pid,
81+
error: {
82+
type: exception.class.name,
83+
message: exception.message,
84+
stack: exception.backtrace&.first(5)&.join(' | ')
85+
},
86+
details: {
87+
url: url.to_s,
88+
request_method: request&.method,
89+
request_uri: request&.uri,
90+
response_body: response&.body
91+
},
92+
schema_version: '1.0.0',
93+
log_version: '1.0.0'
94+
}.to_json)
95+
end
96+
end
97+
98+
class CommunicationError < StandardError
99+
attr_reader :request, :response
100+
101+
def initialize(request = nil, response = nil)
102+
@request = request
103+
@response = response
104+
super("Communication failed with status #{response&.code}")
105+
end
130106
end
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
require 'test_helper'
2+
3+
class RegistryConnectorTest < ActiveSupport::TestCase
4+
def setup
5+
@url = URI('http://registry.test/api/v1/contact_requests/')
6+
@request = Net::HTTP::Post.new(@url)
7+
@request.body = { contact_request: { email: 'test@example.com' } }.to_json
8+
@logger = create_logger_spy
9+
end
10+
11+
{
12+
Timeout::Error => 'network_error',
13+
SocketError => 'network_error',
14+
Errno::ECONNREFUSED => 'network_error'
15+
}.each do |error_class, event_type|
16+
test "logs #{event_type} on #{error_class}" do
17+
perform_with_http_error(error_class.new('Simulated error')) do |result|
18+
assert_not result
19+
end
20+
21+
assert_logged_with_event(event_type)
22+
end
23+
end
24+
25+
test "logs communication_error with response body" do
26+
response = Net::HTTPBadRequest.new('1.1', '400', 'Bad Request')
27+
response.instance_variable_set(:@read, true)
28+
response.body = 'Error message'
29+
30+
http_mock = Minitest::Mock.new
31+
http_mock.expect(:request, response, [@request])
32+
33+
RegistryConnector.stub(:logger, @logger) do
34+
Net::HTTP.stub(:start, ->(*_args, &block) { block.call(http_mock) }) do
35+
result = RegistryConnector.perform_request(@request, @url)
36+
assert_not result
37+
end
38+
end
39+
40+
assert_logged_with_event('communication_error')
41+
log_data = parse_logged_data
42+
assert_equal 'Error message', log_data['details']['response_body']
43+
end
44+
45+
test "logs unexpected_error" do
46+
perform_with_http_error(StandardError.new('Unexpected error')) do |result|
47+
assert_not result
48+
end
49+
50+
assert_logged_with_event('unexpected_error')
51+
end
52+
53+
test "logged data contains required fields" do
54+
perform_with_http_error(Timeout::Error.new('Connection timeout'))
55+
56+
log_data = parse_logged_data
57+
58+
assert log_data['timestamp']
59+
assert_equal 'error', log_data['level']
60+
assert_equal 'registry.api.network_error', log_data['event']
61+
assert_equal 'rest-whois', log_data['service']
62+
assert_equal Rails.env, log_data['environment']
63+
assert log_data['host']
64+
assert log_data['pid']
65+
assert log_data['error']
66+
assert_equal 'Timeout::Error', log_data['error']['type']
67+
assert log_data['error']['message']
68+
assert log_data['details']
69+
assert_equal @url.to_s, log_data['details']['url']
70+
assert_equal 'POST', log_data['details']['request_method']
71+
assert log_data['schema_version']
72+
assert log_data['log_version']
73+
end
74+
75+
test "logged data contains truncated error stack trace" do
76+
error = Timeout::Error.new('Connection timeout')
77+
error.set_backtrace(['line1', 'line2', 'line3', 'line4', 'line5', 'line6'])
78+
79+
perform_with_http_error(error)
80+
81+
log_data = parse_logged_data
82+
stack_trace = log_data['error']['stack']
83+
84+
assert stack_trace
85+
assert_match(/line1/, stack_trace)
86+
assert_match(/line5/, stack_trace)
87+
refute_match(/line6/, stack_trace)
88+
end
89+
90+
private
91+
92+
def perform_with_http_error(error)
93+
RegistryConnector.stub(:logger, @logger) do
94+
Net::HTTP.stub(:start, ->(*_args, &block) { raise error }) do
95+
result = RegistryConnector.perform_request(@request, @url)
96+
yield result if block_given?
97+
result
98+
end
99+
end
100+
end
101+
102+
def create_logger_spy
103+
logged_messages = []
104+
105+
logger_spy = Object.new
106+
logger_spy.define_singleton_method(:error) do |message|
107+
logged_messages << message
108+
end
109+
logger_spy.define_singleton_method(:logged_messages) do
110+
logged_messages
111+
end
112+
113+
logger_spy
114+
end
115+
116+
def assert_logged_with_event(event_type)
117+
assert @logger.logged_messages.any?, "Expected logger.error to be called"
118+
119+
log_data = parse_logged_data
120+
assert_equal "registry.api.#{event_type}", log_data['event']
121+
assert_match(/Registry API #{event_type.gsub('_', ' ')}/, log_data['message'])
122+
end
123+
124+
def parse_logged_data
125+
JSON.parse(@logger.logged_messages.last)
126+
end
127+
end

0 commit comments

Comments
 (0)