diff options
Diffstat (limited to 'test/net')
| -rw-r--r-- | test/net/connection_test.rb | 108 | ||||
| -rw-r--r-- | test/net/content_type_mapper_test.rb | 102 | ||||
| -rw-r--r-- | test/net/error_handling_test.rb | 180 | ||||
| -rw-r--r-- | test/net/timeout_test.rb | 127 |
4 files changed, 517 insertions, 0 deletions
diff --git a/test/net/connection_test.rb b/test/net/connection_test.rb new file mode 100644 index 0000000..380f680 --- /dev/null +++ b/test/net/connection_test.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ConnectionTest < Minitest::Test + def test_initialize_with_http_scheme + connection = Net::Hippie::Connection.new('http', 'example.com', 80) + backend = connection.instance_variable_get(:@backend) + refute backend.instance_variable_get(:@http).use_ssl? + end + + def test_initialize_with_https_scheme + connection = Net::Hippie::Connection.new('https', 'example.com', 443) + backend = connection.instance_variable_get(:@backend) + assert backend.instance_variable_get(:@http).use_ssl? + end + + def test_initialize_with_custom_timeouts + options = { read_timeout: 30, open_timeout: 15 } + connection = Net::Hippie::Connection.new('https', 'example.com', 443, options) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal 30, http.read_timeout + assert_equal 15, http.open_timeout + end + + def test_initialize_with_custom_verify_mode + options = { verify_mode: OpenSSL::SSL::VERIFY_NONE } + connection = Net::Hippie::Connection.new('https', 'example.com', 443, options) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal OpenSSL::SSL::VERIFY_NONE, http.verify_mode + end + + def test_initialize_with_client_certificate + private_key = OpenSSL::PKey::RSA.new(2048) + certificate = OpenSSL::X509::Certificate.new + certificate.not_after = certificate.not_before = Time.now + certificate.public_key = private_key.public_key + certificate.sign(private_key, OpenSSL::Digest::SHA256.new) + + options = { + certificate: certificate.to_pem, + key: private_key.export + } + connection = Net::Hippie::Connection.new('https', 'example.com', 443, options) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal certificate.to_pem, http.cert.to_pem + assert_equal private_key.export, http.key.export + end + + def test_initialize_with_client_certificate_and_passphrase + private_key = OpenSSL::PKey::RSA.new(2048) + passphrase = 'test_passphrase' + certificate = OpenSSL::X509::Certificate.new + certificate.not_after = certificate.not_before = Time.now + certificate.public_key = private_key.public_key + certificate.sign(private_key, OpenSSL::Digest::SHA256.new) + + options = { + certificate: certificate.to_pem, + key: private_key.export(OpenSSL::Cipher.new('AES-256-CBC'), passphrase), + passphrase: passphrase + } + connection = Net::Hippie::Connection.new('https', 'example.com', 443, options) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal certificate.to_pem, http.cert.to_pem + assert_equal private_key.export, http.key.export + end + + def test_run_executes_request + WebMock.stub_request(:get, 'https://example.com/test') + .to_return(status: 200, body: 'success') + + connection = Net::Hippie::Connection.new('https', 'example.com', 443) + request = Net::HTTP::Get.new('/test') + response = connection.run(request) + + assert_equal Net::HTTPOK, response.class + assert_equal 'success', response.body + end + + def test_build_url_for_absolute_path + connection = Net::Hippie::Connection.new('https', 'example.com', 443) + url = connection.build_url_for('https://other.com/path') + assert_equal 'https://other.com/path', url + end + + def test_build_url_for_relative_path_https + connection = Net::Hippie::Connection.new('https', 'example.com', 443) + url = connection.build_url_for('/api/v1/users') + assert_equal 'https://example.com/api/v1/users', url + end + + def test_build_url_for_relative_path_http + connection = Net::Hippie::Connection.new('http', 'example.com', 80) + url = connection.build_url_for('/api/v1/users') + assert_equal 'http://example.com/api/v1/users', url + end + + def test_build_url_for_http_url + connection = Net::Hippie::Connection.new('https', 'example.com', 443) + url = connection.build_url_for('http://other.com/path') + assert_equal 'http://other.com/path', url + end +end
\ No newline at end of file diff --git a/test/net/content_type_mapper_test.rb b/test/net/content_type_mapper_test.rb index 0d8ed64..3802362 100644 --- a/test/net/content_type_mapper_test.rb +++ b/test/net/content_type_mapper_test.rb @@ -24,4 +24,106 @@ class ContentTypeMapperTest < Minitest::Test result = subject.map_from(headers, body) assert_equal body, result end + + def test_returns_string_body_unchanged + subject = Net::Hippie::ContentTypeMapper.new + headers = { 'Content-Type' => 'application/json' } + body = '{"already": "json"}' + result = subject.map_from(headers, body) + assert_equal body, result + end + + def test_returns_json_for_various_json_content_types + subject = Net::Hippie::ContentTypeMapper.new + body = { message: 'test' } + expected = JSON.generate(body) + + json_types = [ + 'application/json', + 'application/json; charset=utf-8', + 'application/json; charset=iso-8859-1', + 'application/vnd.api+json', + 'text/json' + ] + + json_types.each do |content_type| + headers = { 'Content-Type' => content_type } + result = subject.map_from(headers, body) + assert_equal expected, result, "Failed for content type: #{content_type}" + end + end + + def test_returns_hash_body_for_non_json_content_types + subject = Net::Hippie::ContentTypeMapper.new + body = { message: 'test' } + + non_json_types = [ + 'text/plain', + 'text/html', + 'application/xml', + 'application/octet-stream', + 'multipart/form-data' + ] + + non_json_types.each do |content_type| + headers = { 'Content-Type' => content_type } + result = subject.map_from(headers, body) + assert_equal body, result, "Failed for content type: #{content_type}" + end + end + + def test_handles_nil_content_type + subject = Net::Hippie::ContentTypeMapper.new + headers = {} + body = { message: 'test' } + result = subject.map_from(headers, body) + assert_equal body, result + end + + def test_handles_empty_content_type + subject = Net::Hippie::ContentTypeMapper.new + headers = { 'Content-Type' => '' } + body = { message: 'test' } + result = subject.map_from(headers, body) + assert_equal body, result + end + + def test_handles_case_insensitive_content_type_headers + subject = Net::Hippie::ContentTypeMapper.new + body = { message: 'test' } + + # Test various case combinations - current implementation only handles exact 'Content-Type' + # This test documents the current behavior + headers_variations = [ + { 'content-type' => 'application/json' }, + { 'Content-type' => 'application/json' }, + { 'CONTENT-TYPE' => 'application/json' } + ] + + headers_variations.each do |headers| + result = subject.map_from(headers, body) + # Current implementation doesn't handle case-insensitive headers + # so these should return the original body, not JSON + assert_equal body, result + end + end + + def test_handles_complex_json_objects + subject = Net::Hippie::ContentTypeMapper.new + headers = { 'Content-Type' => 'application/json' } + body = { + string: 'test', + number: 123, + boolean: true, + nil_value: nil, + array: [1, 2, 3], + nested: { key: 'value' } + } + result = subject.map_from(headers, body) + assert_equal JSON.generate(body), result + # Verify it's valid JSON by parsing it back + parsed = JSON.parse(result) + expected_parsed = JSON.parse(JSON.generate(body)) + assert_equal expected_parsed, parsed + end end diff --git a/test/net/error_handling_test.rb b/test/net/error_handling_test.rb new file mode 100644 index 0000000..2f963d6 --- /dev/null +++ b/test/net/error_handling_test.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ErrorHandlingTest < Minitest::Test + def setup + @client = Net::Hippie::Client.new + @uri = URI.parse('https://example.com/test') + end + + def test_handles_eof_error + WebMock.stub_request(:get, @uri.to_s).to_raise(EOFError) + + assert_raises EOFError do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_connection_refused + WebMock.stub_request(:get, @uri.to_s).to_raise(Errno::ECONNREFUSED) + + assert_raises Errno::ECONNREFUSED do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_connection_reset + WebMock.stub_request(:get, @uri.to_s).to_raise(Errno::ECONNRESET) + + assert_raises Errno::ECONNRESET do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_host_unreachable + WebMock.stub_request(:get, @uri.to_s).to_raise(Errno::EHOSTUNREACH) + + assert_raises Errno::EHOSTUNREACH do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_invalid_argument + WebMock.stub_request(:get, @uri.to_s).to_raise(Errno::EINVAL) + + assert_raises Errno::EINVAL do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_net_open_timeout + WebMock.stub_request(:get, @uri.to_s).to_raise(Net::OpenTimeout) + + assert_raises Net::OpenTimeout do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_net_protocol_error + WebMock.stub_request(:get, @uri.to_s).to_raise(Net::ProtocolError) + + assert_raises Net::ProtocolError do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_net_read_timeout + WebMock.stub_request(:get, @uri.to_s).to_raise(Net::ReadTimeout) + + assert_raises Net::ReadTimeout do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_openssl_error + WebMock.stub_request(:get, @uri.to_s).to_raise(OpenSSL::OpenSSLError) + + assert_raises OpenSSL::OpenSSLError do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_ssl_error + WebMock.stub_request(:get, @uri.to_s).to_raise(OpenSSL::SSL::SSLError) + + assert_raises OpenSSL::SSL::SSLError do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_socket_error + WebMock.stub_request(:get, @uri.to_s).to_raise(SocketError) + + assert_raises SocketError do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_handles_timeout_error + WebMock.stub_request(:get, @uri.to_s).to_raise(Timeout::Error) + + assert_raises Timeout::Error do + @client.with_retry(retries: 0) { |client| client.get(@uri) } + end + end + + def test_retry_with_exponential_backoff + call_count = 0 + WebMock.stub_request(:get, @uri.to_s).to_return do + call_count += 1 + if call_count < 3 + raise Net::ReadTimeout + else + { status: 200, body: 'success' } + end + end + + start_time = Time.now + response = @client.with_retry(retries: 3) { |client| client.get(@uri) } + end_time = Time.now + + assert_equal Net::HTTPOK, response.class + assert_equal 'success', response.body + assert_equal 3, call_count + # Should have some delay due to exponential backoff + assert_operator end_time - start_time, :>, 0.3 + end + + def test_retry_eventually_fails_after_max_retries + WebMock.stub_request(:get, @uri.to_s).to_raise(Net::ReadTimeout) + + start_time = Time.now + + assert_raises Net::ReadTimeout do + @client.with_retry(retries: 2) { |client| client.get(@uri) } + end + + end_time = Time.now + # Should have attempted 3 times (initial + 2 retries) with delays + assert_operator end_time - start_time, :>, 0.3 + end + + def test_retry_with_nil_retries + WebMock.stub_request(:get, @uri.to_s).to_raise(Net::ReadTimeout) + + assert_raises Net::ReadTimeout do + @client.with_retry(retries: nil) { |client| client.get(@uri) } + end + end + + def test_retry_with_negative_retries + WebMock.stub_request(:get, @uri.to_s).to_raise(Net::ReadTimeout) + + assert_raises Net::ReadTimeout do + @client.with_retry(retries: -1) { |client| client.get(@uri) } + end + end + + def test_connection_errors_constant_includes_all_expected_errors + expected_errors = [ + EOFError, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::ECONNRESET, # Listed twice in original + Errno::EHOSTUNREACH, + Errno::EINVAL, + Net::OpenTimeout, + Net::ProtocolError, + Net::ReadTimeout, + OpenSSL::OpenSSLError, + OpenSSL::SSL::SSLError, + SocketError, + Timeout::Error + ] + + expected_errors.each do |error_class| + assert_includes Net::Hippie::CONNECTION_ERRORS, error_class + end + end +end
\ No newline at end of file diff --git a/test/net/timeout_test.rb b/test/net/timeout_test.rb new file mode 100644 index 0000000..a085b54 --- /dev/null +++ b/test/net/timeout_test.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'test_helper' + +class TimeoutTest < Minitest::Test + def setup + @uri = URI.parse('https://example.com/test') + end + + def test_custom_read_timeout + client = Net::Hippie::Client.new(read_timeout: 5) + connection = client.send(:connection_for, @uri) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal 5, http.read_timeout + end + + def test_custom_open_timeout + client = Net::Hippie::Client.new(open_timeout: 8) + connection = client.send(:connection_for, @uri) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal 8, http.open_timeout + end + + def test_default_timeouts + client = Net::Hippie::Client.new + connection = client.send(:connection_for, @uri) + backend = connection.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal 10, http.read_timeout + assert_equal 10, http.open_timeout + end + + def test_read_timeout_triggers_retry + WebMock.stub_request(:get, @uri.to_s) + .to_timeout.then + .to_return(status: 200, body: 'success') + + client = Net::Hippie::Client.new + response = client.with_retry(retries: 1) { |c| c.get(@uri) } + + assert_equal Net::HTTPOK, response.class + assert_equal 'success', response.body + end + + def test_open_timeout_triggers_retry + WebMock.stub_request(:get, @uri.to_s) + .to_raise(Net::OpenTimeout).then + .to_return(status: 200, body: 'success') + + client = Net::Hippie::Client.new + response = client.with_retry(retries: 1) { |c| c.get(@uri) } + + assert_equal Net::HTTPOK, response.class + assert_equal 'success', response.body + end + + def test_timeout_with_zero_retries + WebMock.stub_request(:get, @uri.to_s).to_timeout + + client = Net::Hippie::Client.new + # WebMock.to_timeout raises different timeout errors, so check for any timeout error + assert_raises(*Net::Hippie::CONNECTION_ERRORS.select { |e| e.name.include?('Timeout') }) do + client.with_retry(retries: 0) { |c| c.get(@uri) } + end + end + + def test_multiple_timeout_types_in_sequence + call_count = 0 + WebMock.stub_request(:get, @uri.to_s).to_return do + call_count += 1 + case call_count + when 1 + raise Net::OpenTimeout + when 2 + raise Net::ReadTimeout + when 3 + raise Timeout::Error + else + { status: 200, body: 'success' } + end + end + + client = Net::Hippie::Client.new + response = client.with_retry(retries: 4) { |c| c.get(@uri) } + + assert_equal Net::HTTPOK, response.class + assert_equal 'success', response.body + assert_equal 4, call_count + end + + def test_timeout_settings_per_connection + uri1 = URI.parse('https://example1.com/test') + uri2 = URI.parse('https://example2.com/test') + + client = Net::Hippie::Client.new(read_timeout: 15, open_timeout: 20) + + connection1 = client.send(:connection_for, uri1) + connection2 = client.send(:connection_for, uri2) + + backend1 = connection1.instance_variable_get(:@backend) + backend2 = connection2.instance_variable_get(:@backend) + http1 = backend1.instance_variable_get(:@http) + http2 = backend2.instance_variable_get(:@http) + + assert_equal 15, http1.read_timeout + assert_equal 20, http1.open_timeout + assert_equal 15, http2.read_timeout + assert_equal 20, http2.open_timeout + end + + def test_timeout_preserves_connection_pooling + client = Net::Hippie::Client.new(read_timeout: 25) + + # First call should create connection + connection1 = client.send(:connection_for, @uri) + # Second call should reuse same connection + connection2 = client.send(:connection_for, @uri) + + assert_same connection1, connection2 + + backend = connection1.instance_variable_get(:@backend) + http = backend.instance_variable_get(:@http) + assert_equal 25, http.read_timeout + end +end
\ No newline at end of file |
