@@ -192,7 +192,27 @@ defmodule ElixirScope.Foundation.Infrastructure.PoolWorkers.HttpWorker do
192192 @ spec do_http_request ( atom ( ) , String . t ( ) , term ( ) , keyword ( ) , state ( ) ) ::
193193 { :ok , map ( ) , state ( ) } | { :error , term ( ) , state ( ) }
194194 defp do_http_request ( method , path , body , options , state ) do
195- url = build_url ( state . base_url , path )
195+ base_url = build_url ( state . base_url , path )
196+
197+ # Handle query parameters
198+ url =
199+ case Keyword . get ( options , :params ) do
200+ nil ->
201+ base_url
202+
203+ params when is_list ( params ) ->
204+ query_string = URI . encode_query ( params )
205+
206+ if String . contains? ( base_url , "?" ) do
207+ "#{ base_url } &#{ query_string } "
208+ else
209+ "#{ base_url } ?#{ query_string } "
210+ end
211+
212+ _ ->
213+ base_url
214+ end
215+
196216 headers = merge_headers ( state . headers , Keyword . get ( options , :headers , [ ] ) )
197217
198218 request_options = [
@@ -248,27 +268,120 @@ defmodule ElixirScope.Foundation.Infrastructure.PoolWorkers.HttpWorker do
248268
249269 @ spec perform_request ( atom ( ) , String . t ( ) , term ( ) , [ { String . t ( ) , String . t ( ) } ] , keyword ( ) ) ::
250270 { :ok , map ( ) } | { :error , term ( ) }
251- defp perform_request ( method , url , body , headers , _options ) do
271+ defp perform_request ( method , url , body , headers , options ) do
252272 # This is a mock implementation - in real usage, you'd use HTTPoison, Finch, etc.
253273 # For demonstration purposes, we'll simulate HTTP requests with deterministic behavior
254274
255- # Simulate network latency
256- Process . sleep ( Enum . random ( 10 .. 100 ) )
275+ # Check for timeout scenarios first
276+ timeout = Keyword . get ( options , :timeout , 30_000 )
257277
258- # Deterministic responses based on URL patterns for testing
259278 cond do
279+ String . contains? ( url , "/delay/" ) ->
280+ # Extract delay seconds from URL pattern like /delay/5
281+ delay_match = Regex . run ( ~r" /delay/(\d +)" , url )
282+
283+ delay_seconds =
284+ case delay_match do
285+ [ _ , seconds_str ] -> String . to_integer ( seconds_str )
286+ _ -> 0
287+ end
288+
289+ # Convert to milliseconds and check against timeout
290+ delay_ms = delay_seconds * 1000
291+
292+ if delay_ms > timeout do
293+ { :error , :timeout }
294+ else
295+ Process . sleep ( delay_ms )
296+ response_body = create_mock_response_body ( method , url , headers , body )
297+ json_body = Jason . encode! ( response_body )
298+
299+ response = % {
300+ status_code: 200 ,
301+ headers: [ { "content-type" , "application/json" } ] ,
302+ body: json_body
303+ }
304+
305+ { :ok , response }
306+ end
307+
308+ String . contains? ( url , "/status/" ) ->
309+ # Extract status code from URL pattern like /status/404
310+ status_match = Regex . run ( ~r" /status/(\d +)" , url )
311+
312+ status_code =
313+ case status_match do
314+ [ _ , code_str ] -> String . to_integer ( code_str )
315+ _ -> 200
316+ end
317+
318+ # Simulate network latency
319+ Process . sleep ( Enum . random ( 10 .. 100 ) )
320+
321+ response_body = create_mock_response_body ( method , url , headers , body )
322+ json_body = Jason . encode! ( response_body )
323+
324+ response = % {
325+ status_code: status_code ,
326+ headers: [ { "content-type" , "application/json" } ] ,
327+ body: json_body
328+ }
329+
330+ { :ok , response }
331+
332+ String . contains? ( url , "/bytes/" ) ->
333+ # Extract byte count from URL pattern like /bytes/10000
334+ bytes_match = Regex . run ( ~r" /bytes/(\d +)" , url )
335+
336+ byte_count =
337+ case bytes_match do
338+ [ _ , count_str ] -> String . to_integer ( count_str )
339+ _ -> 1024
340+ end
341+
342+ # Simulate network latency
343+ Process . sleep ( Enum . random ( 10 .. 100 ) )
344+
345+ # Return raw bytes (not JSON)
346+ response = % {
347+ status_code: 200 ,
348+ headers: [ { "content-type" , "application/octet-stream" } ] ,
349+ body: :crypto . strong_rand_bytes ( byte_count )
350+ }
351+
352+ { :ok , response }
353+
354+ String . contains? ( url , "/xml" ) ->
355+ # Simulate network latency
356+ Process . sleep ( Enum . random ( 10 .. 100 ) )
357+
358+ xml_response = """
359+ <?xml version="1.0" encoding="UTF-8"?>
360+ <response>
361+ <method>#{ method } </method>
362+ <url>#{ url } </url>
363+ </response>
364+ """
365+
366+ response = % {
367+ status_code: 200 ,
368+ headers: [ { "content-type" , "application/xml" } ] ,
369+ body: xml_response
370+ }
371+
372+ { :ok , response }
373+
260374 String . contains? ( url , "/nonexistent" ) ->
261- # Return successful response with 404 status code
375+ # Simulate network latency
376+ Process . sleep ( Enum . random ( 10 .. 100 ) )
377+
378+ response_body = create_mock_response_body ( method , url , headers , body )
379+ json_body = Jason . encode! ( response_body )
380+
262381 response = % {
263382 status_code: 404 ,
264383 headers: [ { "content-type" , "application/json" } ] ,
265- body: % {
266- method: method ,
267- url: url ,
268- timestamp: DateTime . utc_now ( ) ,
269- headers: headers ,
270- body: body
271- }
384+ body: json_body
272385 }
273386
274387 { :ok , response }
@@ -283,23 +396,91 @@ defmodule ElixirScope.Foundation.Infrastructure.PoolWorkers.HttpWorker do
283396 { :error , :timeout }
284397
285398 true ->
286- # Default success response
399+ # Simulate network latency
400+ Process . sleep ( Enum . random ( 10 .. 100 ) )
401+
402+ # Default success response - simulate httpbin.org behavior
403+ response_body = create_httpbin_response ( method , url , headers , body )
404+ json_body = Jason . encode! ( response_body )
405+
287406 response = % {
288407 status_code: 200 ,
289408 headers: [ { "content-type" , "application/json" } ] ,
290- body: % {
291- method: method ,
292- url: url ,
293- timestamp: DateTime . utc_now ( ) ,
294- headers: headers ,
295- body: body
296- }
409+ body: json_body
297410 }
298411
299412 { :ok , response }
300413 end
301414 end
302415
416+ # Helper function to create httpbin.org-style response
417+ defp create_httpbin_response ( method , url , headers , body ) do
418+ # Parse URL to extract query parameters
419+ uri = URI . parse ( url )
420+
421+ query_params =
422+ case uri . query do
423+ nil -> % { }
424+ query_string -> URI . decode_query ( query_string )
425+ end
426+
427+ # Convert headers list to map, ensuring proper key-value format
428+ headers_map =
429+ headers
430+ |> Enum . into ( % { } , fn
431+ { key , value } when is_binary ( key ) and is_binary ( value ) -> { key , value }
432+ other -> { "unknown" , inspect ( other ) }
433+ end )
434+
435+ case method do
436+ :get ->
437+ % {
438+ "args" => query_params ,
439+ "headers" => headers_map ,
440+ "origin" => "127.0.0.1" ,
441+ "url" => url
442+ }
443+
444+ :post ->
445+ # Handle string vs map body encoding
446+ data_string =
447+ case body do
448+ body when is_binary ( body ) -> body
449+ body -> Jason . encode! ( body )
450+ end
451+
452+ % {
453+ "args" => query_params ,
454+ "data" => data_string ,
455+ "files" => % { } ,
456+ "form" => % { } ,
457+ "headers" => headers_map ,
458+ "json" => if ( is_binary ( body ) , do: nil , else: body ) ,
459+ "origin" => "127.0.0.1" ,
460+ "url" => url
461+ }
462+ end
463+ end
464+
465+ # Helper function to create basic mock response body
466+ defp create_mock_response_body ( method , url , headers , body ) do
467+ # Convert headers to a safe format for JSON encoding
468+ headers_map =
469+ headers
470+ |> Enum . into ( % { } , fn
471+ { key , value } when is_binary ( key ) and is_binary ( value ) -> { key , value }
472+ other -> { "unknown" , inspect ( other ) }
473+ end )
474+
475+ % {
476+ method: method ,
477+ url: url ,
478+ timestamp: DateTime . utc_now ( ) ,
479+ headers: headers_map ,
480+ body: body
481+ }
482+ end
483+
303484 @ spec update_stats ( state ( ) , :success | :error , integer ( ) ) :: state ( )
304485 defp update_stats ( state , result , _duration ) do
305486 new_stats = % {
0 commit comments