33require_relative '../../lib/aws_lambda_ric/lambda_errors'
44require_relative '../../lib/aws_lambda_ric/lambda_server'
55require 'net/http'
6+ require 'socket'
67require 'minitest/autorun'
78
89class LambdaServerTest < Minitest ::Test
@@ -37,10 +38,9 @@ def test_post_invocation_error_with_large_xray_cause
3738 headers = { 'Lambda-Runtime-Function-Error-Type' => @error . runtime_error_type ,
3839 'Lambda-Runtime-Function-XRay-Error-Cause' => large_xray_cause ,
3940 'User-Agent' => @mock_user_agent }
40- post_mock = Minitest ::Mock . new
41- post_mock . expect :call , nil , [ @error_uri , @error . to_lambda_response . to_json , headers ]
41+ conn_mock = mock_post_connection ( @error_uri . path , @error . to_lambda_response . to_json , headers )
4242
43- Net ::HTTP . stub ( :post , post_mock ) do
43+ Net ::HTTP . stub ( :new , conn_mock , [ @error_uri . host , @error_uri . port ] ) do
4444 @under_test . send_error_response (
4545 request_id : @request_id ,
4646 error_object : @error . to_lambda_response ,
@@ -49,17 +49,16 @@ def test_post_invocation_error_with_large_xray_cause
4949 )
5050 end
5151
52- assert_mock post_mock
52+ assert_mock conn_mock
5353 end
5454
5555 def test_post_invocation_error_with_too_large_xray_cause
5656 too_large_xray_cause = 'a' * 1024 * 1024
5757 headers = { 'Lambda-Runtime-Function-Error-Type' => @error . runtime_error_type ,
5858 'User-Agent' => @mock_user_agent }
59- post_mock = Minitest ::Mock . new
60- post_mock . expect :call , nil , [ @error_uri , @error . to_lambda_response . to_json , headers ]
59+ conn_mock = mock_post_connection ( @error_uri . path , @error . to_lambda_response . to_json , headers )
6160
62- Net ::HTTP . stub ( :post , post_mock ) do
61+ Net ::HTTP . stub ( :new , conn_mock , [ @error_uri . host , @error_uri . port ] ) do
6362 @under_test . send_error_response (
6463 request_id : @request_id ,
6564 error_object : @error . to_lambda_response ,
@@ -68,7 +67,44 @@ def test_post_invocation_error_with_too_large_xray_cause
6867 )
6968 end
7069
71- assert_mock post_mock
70+ assert_mock conn_mock
71+ end
72+
73+ # Regression: with a proxy in the environment, the response must still reach
74+ # the Runtime API directly
75+ def test_send_response_reaches_api_and_not_proxy_when_proxy_is_set
76+ api = RecordingServer . new
77+ proxy = RecordingServer . new
78+
79+ [ 'HTTP_PROXY' , 'http_proxy' ] . each do |var |
80+ api . reset
81+ proxy . reset
82+ env_stub ( var , "http://#{ proxy . address } " ) do
83+ client = RapidClient . new ( api . address , @mock_user_agent )
84+ client . send_response ( request_id : @request_id , response_object : 'response' )
85+
86+ assert_equal 1 , api . hits , 'response should reach the Runtime API'
87+ assert_equal 0 , proxy . hits , "response must not be routed through #{ var } "
88+ end
89+ end
90+ ensure
91+ api &.close
92+ proxy &.close
93+ end
94+
95+ def mock_post_connection ( path , body , headers )
96+ conn_mock = Minitest ::Mock . new
97+ conn_mock . expect ( :start , nil ) { |&block | block . call ( conn_mock ) }
98+ conn_mock . expect ( :post , nil , [ path , body , headers ] )
99+ conn_mock
100+ end
101+
102+ def env_stub ( name , value )
103+ previous = ENV [ name ]
104+ ENV [ name ] = value
105+ yield
106+ ensure
107+ ENV [ name ] = previous
72108 end
73109
74110 def mock_next_invocation_response ( )
@@ -129,3 +165,49 @@ def test_next_invocation_with_null_tenant_id_header
129165 assert_mock get_mock
130166 end
131167end
168+
169+ # A minimal HTTP server that binds to a non-loopback address and counts the
170+ # requests it receives. Non-loopback matters: Net::HTTP never proxies loopback,
171+ # so a 127.0.0.1 target would bypass the proxy.
172+ class RecordingServer
173+ def initialize
174+ ip = Socket . ip_address_list . find { |a | a . ipv4? && !a . ipv4_loopback? && !a . ipv4_multicast? }
175+ raise 'no non-loopback IPv4 interface available' unless ip
176+
177+ @server = TCPServer . new ( ip . ip_address , 0 )
178+ @hits = 0
179+ @lock = Mutex . new
180+ @thread = Thread . new { accept_loop }
181+ end
182+
183+ def address
184+ "#{ @server . addr [ 3 ] } :#{ @server . addr [ 1 ] } "
185+ end
186+
187+ def hits
188+ @lock . synchronize { @hits }
189+ end
190+
191+ def reset
192+ @lock . synchronize { @hits = 0 }
193+ end
194+
195+ def close
196+ @thread &.kill
197+ @server &.close
198+ end
199+
200+ private
201+
202+ def accept_loop
203+ loop do
204+ client = @server . accept
205+ @lock . synchronize { @hits += 1 }
206+ client . gets
207+ client . write ( "HTTP/1.1 202 Accepted\r \n Content-Length: 0\r \n \r \n " )
208+ client . close
209+ end
210+ rescue IOError , Errno ::EBADF
211+ # server closed
212+ end
213+ end
0 commit comments