Skip to content

Commit 09339e3

Browse files
NSHkrNSHkr
authored andcommitted
fb: 49 properties, 503 tests, 0 failures
1 parent a9036dc commit 09339e3

File tree

1 file changed

+202
-21
lines changed

1 file changed

+202
-21
lines changed

lib/elixir_scope/foundation/infrastructure/pool_workers/http_worker.ex

Lines changed: 202 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)