From c3a16ab6f09e275f6871a91d5de4f28d9c9f141f Mon Sep 17 00:00:00 2001 From: Vitaly Gorodetsky Date: Wed, 27 Nov 2024 18:22:17 +0200 Subject: [PATCH] WIP --- test/hreq/async_test.exs | 69 ------- test/hreq/auth_test.exs | 101 --------- test/hreq/batch/collector_test.exs | 48 ----- test/hreq/batch_test.exs | 80 -------- test/hreq/browser/automation_test.exs | 44 ---- test/hreq/browser_test.exs | 48 ----- test/hreq/cache/storage_test.exs | 46 ----- test/hreq/cache_test.exs | 76 ------- test/hreq/circuit_breaker_test.exs | 74 ------- test/hreq/client_generator_test.exs | 191 ------------------ test/hreq/client_test.exs | 150 -------------- test/hreq/compression_test.exs | 54 ----- test/hreq/config_test.exs | 117 ----------- test/hreq/cookies_test.exs | 92 --------- test/hreq/error_test.exs | 47 ----- test/hreq/logger_test.exs | 143 ------------- test/hreq/metrics/reporter_test.exs | 61 ------ test/hreq/metrics_test.exs | 100 --------- test/hreq/middleware/auth_test.exs | 47 ----- test/hreq/middleware/cache_test.exs | 68 ------- test/hreq/middleware/circuit_breaker_test.exs | 44 ---- test/hreq/middleware/compression_test.exs | 45 ----- test/hreq/middleware/logger_test.exs | 63 ------ test/hreq/middleware/oauth2_test.exs | 80 -------- test/hreq/middleware/pipeline_test.exs | 92 --------- test/hreq/middleware/prepare_request_test.exs | 92 --------- test/hreq/middleware/proxy_rotation_test.exs | 56 ----- test/hreq/middleware/rate_limit_test.exs | 56 ----- test/hreq/middleware/rate_limiter_test.exs | 52 ----- test/hreq/middleware/retry_test.exs | 61 ------ test/hreq/middleware/signing_test.exs | 45 ----- test/hreq/middleware/validator_test.exs | 67 ------ test/hreq/middleware_test.exs | 56 ----- test/hreq/oauth2_test.exs | 82 -------- test/hreq/pipeline_test.exs | 76 ------- test/hreq/proxy_test.exs | 65 ------ test/hreq/rate_limit_test.exs | 62 ------ test/hreq/response_test.exs | 74 ------- test/hreq/retry_test.exs | 71 ------- test/hreq/session_store_test.exs | 52 ----- test/hreq/session_test.exs | 55 ----- test/hreq/signing_test.exs | 71 ------- test/hreq/streaming_test.exs | 84 -------- test/hreq/telemetry_test.exs | 115 ----------- test/hreq/utils_test.exs | 53 ----- test/hreq/validator_test.exs | 180 ----------------- test/hreq/websocket_test.exs | 94 --------- test/hreq_test.exs | 139 ------------- 48 files changed, 3738 deletions(-) delete mode 100644 test/hreq/async_test.exs delete mode 100644 test/hreq/auth_test.exs delete mode 100644 test/hreq/batch/collector_test.exs delete mode 100644 test/hreq/batch_test.exs delete mode 100644 test/hreq/browser/automation_test.exs delete mode 100644 test/hreq/browser_test.exs delete mode 100644 test/hreq/cache/storage_test.exs delete mode 100644 test/hreq/cache_test.exs delete mode 100644 test/hreq/circuit_breaker_test.exs delete mode 100644 test/hreq/client_generator_test.exs delete mode 100644 test/hreq/client_test.exs delete mode 100644 test/hreq/compression_test.exs delete mode 100644 test/hreq/config_test.exs delete mode 100644 test/hreq/cookies_test.exs delete mode 100644 test/hreq/error_test.exs delete mode 100644 test/hreq/logger_test.exs delete mode 100644 test/hreq/metrics/reporter_test.exs delete mode 100644 test/hreq/metrics_test.exs delete mode 100644 test/hreq/middleware/auth_test.exs delete mode 100644 test/hreq/middleware/cache_test.exs delete mode 100644 test/hreq/middleware/circuit_breaker_test.exs delete mode 100644 test/hreq/middleware/compression_test.exs delete mode 100644 test/hreq/middleware/logger_test.exs delete mode 100644 test/hreq/middleware/oauth2_test.exs delete mode 100644 test/hreq/middleware/pipeline_test.exs delete mode 100644 test/hreq/middleware/prepare_request_test.exs delete mode 100644 test/hreq/middleware/proxy_rotation_test.exs delete mode 100644 test/hreq/middleware/rate_limit_test.exs delete mode 100644 test/hreq/middleware/rate_limiter_test.exs delete mode 100644 test/hreq/middleware/retry_test.exs delete mode 100644 test/hreq/middleware/signing_test.exs delete mode 100644 test/hreq/middleware/validator_test.exs delete mode 100644 test/hreq/middleware_test.exs delete mode 100644 test/hreq/oauth2_test.exs delete mode 100644 test/hreq/pipeline_test.exs delete mode 100644 test/hreq/proxy_test.exs delete mode 100644 test/hreq/rate_limit_test.exs delete mode 100644 test/hreq/response_test.exs delete mode 100644 test/hreq/retry_test.exs delete mode 100644 test/hreq/session_store_test.exs delete mode 100644 test/hreq/session_test.exs delete mode 100644 test/hreq/signing_test.exs delete mode 100644 test/hreq/streaming_test.exs delete mode 100644 test/hreq/telemetry_test.exs delete mode 100644 test/hreq/utils_test.exs delete mode 100644 test/hreq/validator_test.exs delete mode 100644 test/hreq/websocket_test.exs delete mode 100644 test/hreq_test.exs diff --git a/test/hreq/async_test.exs b/test/hreq/async_test.exs deleted file mode 100644 index 066cbb0..0000000 --- a/test/hreq/async_test.exs +++ /dev/null @@ -1,69 +0,0 @@ -defmodule HReq.AsyncTest do - use ExUnit.Case - - alias HReq.Async - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass} - end - - describe "async requests" do - test "performs async request", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/", fn conn -> - Process.sleep(100) # Simulate delay - Plug.Conn.resp(conn, 200, "ok") - end) - - url = "http://localhost:#{bypass.port}/" - task = Async.request(:get, url) - - assert {:ok, response} = Task.await(task) - assert response.status == 200 - end - - test "performs multiple concurrent requests", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/1", fn conn -> - Process.sleep(50) - Plug.Conn.resp(conn, 200, "first") - end) - - Bypass.expect(bypass, "GET", "/2", fn conn -> - Process.sleep(50) - Plug.Conn.resp(conn, 200, "second") - end) - - requests = [ - {:get, "http://localhost:#{bypass.port}/1", []}, - {:get, "http://localhost:#{bypass.port}/2", []} - ] - - results = Async.map(requests) - - assert length(results) == 2 - assert Enum.all?(results, fn - {:ok, response} -> response.status == 200 - _ -> false - end) - end - - test "throttles concurrent requests", %{bypass: bypass} do - requests = for i <- 1..5 do - Bypass.expect(bypass, "GET", "/#{i}", fn conn -> - Process.sleep(50) - Plug.Conn.resp(conn, 200, "response #{i}") - end) - - {:get, "http://localhost:#{bypass.port}/#{i}", []} - end - - results = Async.map_throttle(requests, 2) - - assert length(results) == 5 - assert Enum.all?(results, fn - {:ok, response} -> response.status == 200 - _ -> false - end) - end - end -end diff --git a/test/hreq/auth_test.exs b/test/hreq/auth_test.exs deleted file mode 100644 index 31b411c..0000000 --- a/test/hreq/auth_test.exs +++ /dev/null @@ -1,101 +0,0 @@ -defmodule HReq.AuthTest do - use ExUnit.Case - alias HReq.{Auth, Config} - - describe "Basic auth" do - test "adds basic auth header" do - config = Config.new( - auth: %{ - type: :basic, - username: "user", - password: "pass" - } - ) - - {:ok, config} = Auth.Basic.authenticate(config) - assert {"authorization", "Basic " <> encoded} = List.keyfind(config.headers, "authorization", 0) - assert Base.decode64!(encoded) == "user:pass" - end - - test "handles missing credentials" do - config = Config.new(auth: %{type: :basic}) - assert {:error, _} = Auth.Basic.authenticate(config) - end - end - - describe "Bearer auth" do - test "adds bearer token header" do - config = Config.new( - auth: %{ - type: :bearer, - token: "my-token" - } - ) - - {:ok, config} = Auth.Bearer.authenticate(config) - assert {"authorization", "Bearer my-token"} = List.keyfind(config.headers, "authorization", 0) - end - - test "handles missing token" do - config = Config.new(auth: %{type: :bearer}) - assert {:error, _} = Auth.Bearer.authenticate(config) - end - end - - describe "OAuth2 auth" do - setup do - bypass = Bypass.open() - {:ok, bypass: bypass} - end - - test "performs OAuth2 token request", %{bypass: bypass} do - Bypass.expect_once(bypass, "POST", "/token", fn conn -> - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, ~s({"access_token": "new-token"})) - end) - - config = Config.new( - auth: %{ - type: :oauth2, - client_id: "client-id", - client_secret: "client-secret", - token_url: "http://localhost:#{bypass.port}/token" - } - ) - - {:ok, config} = Auth.OAuth2.authenticate(config) - assert {"authorization", "Bearer new-token"} = List.keyfind(config.headers, "authorization", 0) - end - end - - describe "API Key auth" do - test "adds API key to headers" do - config = Config.new( - auth: %{ - type: :apikey, - key: "secret-key", - name: "x-api-key", - location: :header - } - ) - - {:ok, config} = Auth.ApiKey.authenticate(config) - assert {"x-api-key", "secret-key"} = List.keyfind(config.headers, "x-api-key", 0) - end - - test "adds API key to query parameters" do - config = Config.new( - auth: %{ - type: :apikey, - key: "secret-key", - name: "api_key", - location: :query - } - ) - - {:ok, config} = Auth.ApiKey.authenticate(config) - assert config.params["api_key"] == "secret-key" - end - end -end diff --git a/test/hreq/batch/collector_test.exs b/test/hreq/batch/collector_test.exs deleted file mode 100644 index 054549d..0000000 --- a/test/hreq/batch/collector_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -defmodule HReq.Batch.CollectorTest do - use ExUnit.Case - alias HReq.{Batch.Collector, Response} - - describe "response collectors" do - test "collects JSON field values" do - responses = [ - %Response{body: ~s({"user": {"name": "Alice"}}), status: 200}, - %Response{body: ~s({"user": {"name": "Bob"}}), status: 200} - ] - - collector = Collector.json_field("user.name") - assert {:ok, names} = Collector.collect(responses, collector) - assert names == ["Alice", "Bob"] - end - - test "handles JSON field not found" do - responses = [ - %Response{body: ~s({"user": {}}), status: 200} - ] - - collector = Collector.json_field("user.name") - assert {:error, error} = Collector.collect(responses, collector) - assert error.reason == :field_not_found - end - - test "collects regex matches" do - responses = [ - %Response{body: "Page 1", status: 200}, - %Response{body: "Page 2", status: 200} - ] - - collector = Collector.regex_match(~r/(.*?)<\/title>/) - assert {:ok, titles} = Collector.collect(responses, collector) - assert titles == [["Page 1"], ["Page 2"]] - end - - test "handles regex pattern not found" do - responses = [ - %Response{body: "No title here", status: 200} - ] - - collector = Collector.regex_match(~r/<title>(.*?)<\/title>/) - assert {:error, error} = Collector.collect(responses, collector) - assert error.reason == :pattern_not_found - end - end -end diff --git a/test/hreq/batch_test.exs b/test/hreq/batch_test.exs deleted file mode 100644 index b90f4af..0000000 --- a/test/hreq/batch_test.exs +++ /dev/null @@ -1,80 +0,0 @@ -defmodule HReq.BatchTest do - use ExUnit.Case - alias HReq.{Batch, Config, Response} - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass} - end - - describe "batch requests" do - test "executes parallel requests", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/api/1", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"id": 1})) - end) - - Bypass.expect(bypass, "GET", "/api/2", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"id": 2})) - end) - - batch = Batch.new() - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/1") - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/2") - - assert {:ok, responses} = Batch.execute(batch) - assert length(responses) == 2 - assert Enum.all?(responses, & &1.status == 200) - end - - test "executes sequential requests", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/api/1", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"id": 1})) - end) - - Bypass.expect(bypass, "GET", "/api/2", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"id": 2})) - end) - - batch = Batch.new(Config.new(), strategy: :sequential) - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/1") - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/2") - - assert {:ok, responses} = Batch.execute(batch) - assert length(responses) == 2 - end - - test "handles request failures", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/api/1", fn conn -> - Plug.Conn.resp(conn, 500, "error") - end) - - batch = Batch.new() - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/1") - - assert {:error, error} = Batch.execute(batch) - assert error.reason == :http_error - end - - test "aggregates responses", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/api/1", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"value": 1})) - end) - - Bypass.expect(bypass, "GET", "/api/2", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"value": 2})) - end) - - batch = Batch.new() - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/1") - |> Batch.add(:get, "http://localhost:#{bypass.port}/api/2") - - {:ok, responses} = Batch.execute(batch) - results = Batch.aggregate(responses, fn response -> - {:ok, json} = Jason.decode(response.body) - json["value"] - end) - - assert results == [1, 2] - end - end -end diff --git a/test/hreq/browser/automation_test.exs b/test/hreq/browser/automation_test.exs deleted file mode 100644 index bf30e04..0000000 --- a/test/hreq/browser/automation_test.exs +++ /dev/null @@ -1,44 +0,0 @@ -defmodule HReq.Browser.AutomationTest do - use ExUnit.Case - - alias HReq.Browser.Automation - - @moduletag :browser - - setup do - {:ok, session} = Automation.start_session() - on_exit(fn -> Wallaby.end_session(session) end) - {:ok, session: session} - end - - describe "start_session/1" do - test "starts browser session" do - assert {:ok, session} = Automation.start_session() - assert is_struct(session, Wallaby.Session) - end - end - - describe "visit/2" do - test "visits URL", %{session: session} do - assert {:ok, session} = Automation.visit(session, "http://example.com") - assert session.url == "http://example.com" - end - end - - describe "take_screenshot/2" do - test "takes screenshot", %{session: session} do - path = "test/screenshots/test.png" - assert {:ok, ^path} = Automation.take_screenshot(session, path) - assert File.exists?(path) - File.rm(path) - end - end - - describe "execute_script/3" do - test "executes JavaScript", %{session: session} do - script = "return document.title;" - assert {:ok, title} = Automation.execute_script(session, script) - assert is_binary(title) - end - end -end diff --git a/test/hreq/browser_test.exs b/test/hreq/browser_test.exs deleted file mode 100644 index 34aa06d..0000000 --- a/test/hreq/browser_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -defmodule HReq.BrowserTest do - use ExUnit.Case - - alias HReq.{Browser, Config} - - @moduletag :integration - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass} - end - - describe "browser-based requests" do - test "performs GET request with browser", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/", fn conn -> - conn - |> Plug.Conn.put_resp_content_type("text/html") - |> Plug.Conn.resp(200, "<html><body>Hello</body></html>") - end) - - url = "http://localhost:#{bypass.port}/" - - config = - Config.new( - browser_mode: true, - browser_options: %{headless: true} - ) - - assert {:ok, response} = Browser.request(:get, url, config) - assert response.status == 200 - assert response.body =~ "Hello" - end - - test "handles cookies in browser mode", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/", fn conn -> - conn - |> Plug.Conn.put_resp_header("set-cookie", "session=123") - |> Plug.Conn.resp(200, "<html><body>Hello</body></html>") - end) - - url = "http://localhost:#{bypass.port}/" - config = Config.new(browser_mode: true) - - assert {:ok, response} = Browser.request(:get, url, config) - assert response.cookies["session"] == "123" - end - end -end diff --git a/test/hreq/cache/storage_test.exs b/test/hreq/cache/storage_test.exs deleted file mode 100644 index 251aeff..0000000 --- a/test/hreq/cache/storage_test.exs +++ /dev/null @@ -1,46 +0,0 @@ -defmodule HReq.Cache.StorageTest do - use ExUnit.Case - alias HReq.Cache.Storage.Memory - - setup do - start_supervised!({Memory, name: :test_cache}) - {:ok, cache: :test_cache} - end - - describe "memory storage" do - test "stores and retrieves values", %{cache: cache} do - assert :ok = Memory.put("key1", "value1", [], cache) - assert {:ok, "value1"} = Memory.get("key1", cache) - end - - test "handles TTL expiration", %{cache: cache} do - assert :ok = Memory.put("key1", "value1", [ttl: 1], cache) - assert {:ok, "value1"} = Memory.get("key1", cache) - - :timer.sleep(1100) - assert {:error, :not_found} = Memory.get("key1", cache) - end - - test "deletes entries", %{cache: cache} do - Memory.put("key1", "value1", [], cache) - assert :ok = Memory.delete("key1", cache) - assert {:error, :not_found} = Memory.get("key1", cache) - end - - test "clears all entries", %{cache: cache} do - Memory.put("key1", "value1", [], cache) - Memory.put("key2", "value2", [], cache) - - assert :ok = Memory.clear(cache) - assert {:error, :not_found} = Memory.get("key1", cache) - assert {:error, :not_found} = Memory.get("key2", cache) - end - - test "updates existing entries", %{cache: cache} do - Memory.put("key1", "value1", [ttl: 10], cache) - Memory.put("key1", "value2", [ttl: 10], cache) - - assert {:ok, "value2"} = Memory.get("key1", cache) - end - end -end diff --git a/test/hreq/cache_test.exs b/test/hreq/cache_test.exs deleted file mode 100644 index bc1abc5..0000000 --- a/test/hreq/cache_test.exs +++ /dev/null @@ -1,76 +0,0 @@ -defmodule HReq.CacheTest do - use ExUnit.Case - - alias HReq.{Cache, Config, Response} - - setup do - start_supervised!(HReq.Cache.Memory) - :ok - end - - describe "get/1" do - test "returns cached response" do - config = %Config{ - method: :get, - url: "http://test.com", - cache: %{backend: :memory, ttl: 60} - } - - response = %Response{status: 200, body: "test"} - :ok = Cache.put(config, response) - - assert {:ok, ^response} = Cache.get(config) - end - - test "returns error when not found" do - config = %Config{ - method: :get, - url: "http://test.com", - cache: %{backend: :memory} - } - - assert {:error, _} = Cache.get(config) - end - end - - describe "put/2" do - test "caches successful GET responses" do - config = %Config{ - method: :get, - url: "http://test.com", - cache: %{backend: :memory} - } - - response = %Response{status: 200, body: "test"} - assert :ok = Cache.put(config, response) - end - - test "doesn't cache non-GET requests" do - config = %Config{ - method: :post, - url: "http://test.com", - cache: %{backend: :memory} - } - - response = %Response{status: 200, body: "test"} - assert :ok = Cache.put(config, response) - assert {:error, _} = Cache.get(config) - end - end - - describe "invalidate/1" do - test "removes cached response" do - config = %Config{ - method: :get, - url: "http://test.com", - cache: %{backend: :memory} - } - - response = %Response{status: 200, body: "test"} - :ok = Cache.put(config, response) - :ok = Cache.invalidate(config) - - assert {:error, _} = Cache.get(config) - end - end -end diff --git a/test/hreq/circuit_breaker_test.exs b/test/hreq/circuit_breaker_test.exs deleted file mode 100644 index ff397ed..0000000 --- a/test/hreq/circuit_breaker_test.exs +++ /dev/null @@ -1,74 +0,0 @@ -defmodule HReq.CircuitBreakerTest do - use ExUnit.Case - alias HReq.CircuitBreaker - - setup do - {:ok, pid} = - CircuitBreaker.start_link( - name: :test_breaker, - failure_threshold: 3, - reset_timeout: 100, - half_open_limit: 1 - ) - - {:ok, breaker: :test_breaker} - end - - describe "circuit breaker" do - test "allows successful calls", %{breaker: breaker} do - assert {:ok, result} = CircuitBreaker.call(breaker, fn -> {:ok, :success} end) - assert result == :success - assert CircuitBreaker.state(breaker) == :closed - end - - test "tracks failures", %{breaker: breaker} do - # First failure - assert {:error, _} = CircuitBreaker.call(breaker, fn -> {:error, :test_error} end) - assert CircuitBreaker.state(breaker) == :closed - - # Second failure - assert {:error, _} = CircuitBreaker.call(breaker, fn -> {:error, :test_error} end) - assert CircuitBreaker.state(breaker) == :closed - - # Third failure - should open circuit - assert {:error, _} = CircuitBreaker.call(breaker, fn -> {:error, :test_error} end) - assert CircuitBreaker.state(breaker) == :open - end - - test "rejects calls when open", %{breaker: breaker} do - # Open the circuit - for _ <- 1..3 do - CircuitBreaker.call(breaker, fn -> {:error, :test_error} end) - end - - assert {:error, error} = CircuitBreaker.call(breaker, fn -> {:ok, :success} end) - assert error.reason == :circuit_open - end - - test "transitions to half-open after timeout", %{breaker: breaker} do - # Open the circuit - for _ <- 1..3 do - CircuitBreaker.call(breaker, fn -> {:error, :test_error} end) - end - - # Wait for reset timeout - Process.sleep(150) - - # First call should be allowed (half-open) - assert {:ok, :success} = CircuitBreaker.call(breaker, fn -> {:ok, :success} end) - assert CircuitBreaker.state(breaker) == :closed - end - - test "resets to closed state", %{breaker: breaker} do - # Open the circuit - for _ <- 1..3 do - CircuitBreaker.call(breaker, fn -> {:error, :test_error} end) - end - - assert :ok = CircuitBreaker.reset(breaker) - assert CircuitBreaker.state(breaker) == :closed - - assert {:ok, :success} = CircuitBreaker.call(breaker, fn -> {:ok, :success} end) - end - end -end diff --git a/test/hreq/client_generator_test.exs b/test/hreq/client_generator_test.exs deleted file mode 100644 index b533c95..0000000 --- a/test/hreq/client_generator_test.exs +++ /dev/null @@ -1,191 +0,0 @@ -defmodule HReq.ClientGeneratorTest do - use ExUnit.Case - alias HReq.{ClientGenerator, Error} - - @petstore_spec %{ - "openapi" => "3.0.0", - "info" => %{ - "title" => "Petstore API", - "version" => "1.0.0" - }, - "servers" => [ - %{"url" => "https://petstore.example.com/v1"} - ], - "paths" => %{ - "/pets" => %{ - "get" => %{ - "operationId" => "listPets", - "summary" => "List all pets", - "description" => "Returns a list of pets", - "parameters" => [ - %{ - "name" => "limit", - "in" => "query", - "required" => false, - "schema" => %{"type" => "integer"} - } - ], - "responses" => %{ - "200" => %{ - "description" => "A list of pets", - "content" => %{ - "application/json" => %{ - "schema" => %{"$ref" => "#/components/schemas/Pets"} - } - } - } - } - }, - "post" => %{ - "operationId" => "createPet", - "summary" => "Create a pet", - "description" => "Creates a new pet", - "requestBody" => %{ - "content" => %{ - "application/json" => %{ - "schema" => %{"$ref" => "#/components/schemas/NewPet"} - } - } - }, - "responses" => %{ - "201" => %{ - "description" => "Pet created", - "content" => %{ - "application/json" => %{ - "schema" => %{"$ref" => "#/components/schemas/Pet"} - } - } - } - } - } - }, - "/pets/{id}" => %{ - "get" => %{ - "operationId" => "getPetById", - "summary" => "Get pet by ID", - "description" => "Returns a pet by ID", - "parameters" => [ - %{ - "name" => "id", - "in" => "path", - "required" => true, - "schema" => %{"type" => "string"} - } - ], - "responses" => %{ - "200" => %{ - "description" => "Pet found", - "content" => %{ - "application/json" => %{ - "schema" => %{"$ref" => "#/components/schemas/Pet"} - } - } - } - } - } - } - }, - "components" => %{ - "schemas" => %{ - "Pet" => %{ - "type" => "object", - "properties" => %{ - "id" => %{"type" => "string"}, - "name" => %{"type" => "string"}, - "age" => %{"type" => "integer"} - } - }, - "Pets" => %{ - "type" => "array", - "items" => %{"$ref" => "#/components/schemas/Pet"} - }, - "NewPet" => %{ - "type" => "object", - "properties" => %{ - "name" => %{"type" => "string"}, - "age" => %{"type" => "integer"} - } - } - } - } - } - - describe "client generation" do - test "generates client module from valid spec" do - assert {:ok, module} = ClientGenerator.generate(@petstore_spec, %{ - module: PetstoreClient, - base_url: "https://petstore.example.com/v1", - default_headers: [{"accept", "application/json"}], - schemas: true - }) - - assert function_exported?(module, :list_pets, 1) - assert function_exported?(module, :create_pet, 1) - assert function_exported?(module, :get_pet_by_id, 2) - end - - test "generates type specs when enabled" do - {:ok, _} = ClientGenerator.generate(@petstore_spec, %{ - module: PetstoreClientWithTypes, - schemas: true - }) - - types = PetstoreClientWithTypes.module_info(:attributes)[:type] || [] - type_names = Enum.map(types, fn {name, _, _} -> name end) - - assert :Pet in type_names - assert :Pets in type_names - assert :NewPet in type_names - end - - test "handles invalid spec" do - assert {:error, %Error{reason: :invalid_spec}} = - ClientGenerator.generate("invalid json", %{module: InvalidClient}) - end - - test "requires module option" do - assert_raise ArgumentError, "module option is required", fn -> - ClientGenerator.generate(@petstore_spec, %{}) - end - end - end - - describe "generated client" do - setup do - bypass = Bypass.open() - {:ok, _} = ClientGenerator.generate(@petstore_spec, %{ - module: TestPetstoreClient, - base_url: "http://localhost:#{bypass.port}", - default_headers: [{"accept", "application/json"}] - }) - {:ok, bypass: bypass} - end - - test "makes requests with correct path parameters", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/pets/123", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"id": "123", "name": "Max", "age": 5})) - end) - - assert {:ok, response} = TestPetstoreClient.get_pet_by_id("123") - assert response.body =~ "Max" - end - - test "makes requests with query parameters", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/pets", fn conn -> - assert conn.query_string == "limit=10" - Plug.Conn.resp(conn, 200, ~s([{"id": "1", "name": "Max"}])) - end) - - assert {:ok, _} = TestPetstoreClient.list_pets(limit: 10) - end - - test "includes default headers", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/pets", fn conn -> - assert Plug.Conn.get_req_header(conn, "accept") == ["application/json"] - Plug.Conn.resp(conn, 200, "[]") - end) - - assert {:ok, _} = TestPetstoreClient.list_pets() - end - end -end diff --git a/test/hreq/client_test.exs b/test/hreq/client_test.exs deleted file mode 100644 index 4ca178f..0000000 --- a/test/hreq/client_test.exs +++ /dev/null @@ -1,150 +0,0 @@ -defmodule HReq.ClientTest do - use ExUnit.Case - - alias HReq.{Client, Config, Response, Error} - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass, url: "http://localhost:#{bypass.port}"} - end - - describe "request/1" do - test "performs successful request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/test", fn conn -> - Plug.Conn.resp(conn, 200, "success") - end) - - config = Config.new(method: :get, url: "#{url}/test") - assert {:ok, %Response{status: 200, body: "success"}} = Client.request(config) - end - - test "handles connection errors", %{url: url} do - config = Config.new(method: :get, url: "#{url}/error") - assert {:error, %Error{reason: :network_error}} = Client.request(config) - end - - test "handles timeouts", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/timeout", fn conn -> - Process.sleep(100) - Plug.Conn.resp(conn, 200, "") - end) - - config = Config.new( - method: :get, - url: "#{url}/timeout", - recv_timeout: 50 - ) - - assert {:error, %Error{reason: :timeout}} = Client.request(config) - end - - test "follows redirects", %{bypass: bypass, url: url} do - Bypass.expect(bypass, "GET", "/redirect", fn conn -> - conn - |> Plug.Conn.put_resp_header("location", "#{url}/final") - |> Plug.Conn.resp(302, "") - end) - - Bypass.expect(bypass, "GET", "/final", fn conn -> - Plug.Conn.resp(conn, 200, "success") - end) - - config = Config.new( - method: :get, - url: "#{url}/redirect", - follow_redirects: true - ) - - assert {:ok, %Response{status: 200, body: "success"}} = Client.request(config) - end - - test "respects max redirects", %{bypass: bypass, url: url} do - Bypass.expect(bypass, "GET", "/redirect", fn conn -> - conn - |> Plug.Conn.put_resp_header("location", "#{url}/redirect") - |> Plug.Conn.resp(302, "") - end) - - config = Config.new( - method: :get, - url: "#{url}/redirect", - follow_redirects: true, - max_redirects: 2 - ) - - assert {:error, %Error{reason: :too_many_redirects}} = Client.request(config) - end - - test "handles invalid redirect location", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/redirect", fn conn -> - conn - |> Plug.Conn.put_resp_header("location", "invalid-url") - |> Plug.Conn.resp(302, "") - end) - - config = Config.new( - method: :get, - url: "#{url}/redirect", - follow_redirects: true - ) - - assert {:error, %Error{reason: :invalid_redirect}} = Client.request(config) - end - end - - describe "get/3" do - test "performs GET request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/get", fn conn -> - Plug.Conn.resp(conn, 200, "get success") - end) - - assert {:ok, %Response{status: 200, body: "get success"}} = Client.get("#{url}/get") - end - end - - describe "post/4" do - test "performs POST request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "POST", "/post", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert body == "post data" - Plug.Conn.resp(conn, 201, "post success") - end) - - assert {:ok, %Response{status: 201, body: "post success"}} = Client.post("#{url}/post", "post data") - end - end - - describe "put/4" do - test "performs PUT request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "PUT", "/put", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert body == "put data" - Plug.Conn.resp(conn, 200, "put success") - end) - - assert {:ok, %Response{status: 200, body: "put success"}} = Client.put("#{url}/put", "put data") - end - end - - describe "patch/4" do - test "performs PATCH request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "PATCH", "/patch", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert body == "patch data" - Plug.Conn.resp(conn, 200, "patch success") - end) - - assert {:ok, %Response{status: 200, body: "patch success"}} = Client.patch("#{url}/patch", "patch data") - end - end - - describe "delete/3" do - test "performs DELETE request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "DELETE", "/delete", fn conn -> - Plug.Conn.resp(conn, 204, "") - end) - - assert {:ok, %Response{status: 204, body: ""}} = Client.delete("#{url}/delete") - end - end -end diff --git a/test/hreq/compression_test.exs b/test/hreq/compression_test.exs deleted file mode 100644 index b76e21f..0000000 --- a/test/hreq/compression_test.exs +++ /dev/null @@ -1,54 +0,0 @@ -defmodule HReq.CompressionTest do - use ExUnit.Case - - alias HReq.{Compression, Config, Response} - - describe "compress_request/1" do - test "compresses request body" do - config = %Config{ - method: :post, - body: "test data", - headers: [] - } - - compressed = Compression.compress_request(config) - assert {"content-encoding", "gzip"} in compressed.headers - assert compressed.body != config.body - end - - test "skips compression if already compressed" do - config = %Config{ - method: :post, - body: "test data", - headers: [{"content-encoding", "gzip"}] - } - - assert Compression.compress_request(config) == config - end - end - - describe "decompress_response/1" do - test "decompresses gzipped response" do - body = :zlib.gzip("test data") - - response = %Response{ - status: 200, - body: body, - headers: [{"content-encoding", "gzip"}] - } - - decompressed = Compression.decompress_response(response) - assert decompressed.body == "test data" - end - - test "handles uncompressed response" do - response = %Response{ - status: 200, - body: "test data", - headers: [] - } - - assert Compression.decompress_response(response) == response - end - end -end diff --git a/test/hreq/config_test.exs b/test/hreq/config_test.exs deleted file mode 100644 index ad4d8ff..0000000 --- a/test/hreq/config_test.exs +++ /dev/null @@ -1,117 +0,0 @@ -defmodule HReq.ConfigTest do - use ExUnit.Case - - alias HReq.Config - - describe "new/1" do - test "creates new config with defaults" do - config = Config.new(method: :get, url: "http://example.com") - - assert config.method == :get - assert config.url == "http://example.com" - assert config.headers == [] - assert config.params == %{} - assert config.body == nil - end - - test "validates required fields" do - assert_raise ArgumentError, fn -> Config.new([]) end - assert_raise ArgumentError, fn -> Config.new(method: :get) end - assert_raise ArgumentError, fn -> Config.new(url: "http://example.com") end - end - - test "validates method" do - assert_raise ArgumentError, fn -> - Config.new(method: :invalid, url: "http://example.com") - end - end - - test "validates URL format" do - assert_raise ArgumentError, fn -> - Config.new(method: :get, url: "invalid-url") - end - end - end - - describe "merge/2" do - test "merges options with existing config" do - config = Config.new( - method: :get, - url: "http://example.com", - headers: [{"accept", "application/json"}] - ) - - merged = Config.merge(config, headers: [{"content-type", "application/json"}]) - - assert merged.headers == [{"content-type", "application/json"}, {"accept", "application/json"}] - end - - test "overrides existing values" do - config = Config.new( - method: :get, - url: "http://example.com", - params: %{key: "old"} - ) - - merged = Config.merge(config, params: %{key: "new"}) - - assert merged.params == %{key: "new"} - end - end - - describe "put_header/3" do - test "adds new header" do - config = Config.new(method: :get, url: "http://example.com") - config = Config.put_header(config, "content-type", "application/json") - - assert {"content-type", "application/json"} in config.headers - end - - test "replaces existing header" do - config = Config.new( - method: :get, - url: "http://example.com", - headers: [{"content-type", "text/plain"}] - ) - - config = Config.put_header(config, "content-type", "application/json") - - assert config.headers == [{"content-type", "application/json"}] - end - end - - describe "get_header/2" do - test "returns header value" do - config = Config.new( - method: :get, - url: "http://example.com", - headers: [{"content-type", "application/json"}] - ) - - assert Config.get_header(config, "content-type") == "application/json" - assert Config.get_header(config, "missing") == nil - assert Config.get_header(config, "missing", "default") == "default" - end - end - - describe "put_param/3" do - test "adds new param" do - config = Config.new(method: :get, url: "http://example.com") - config = Config.put_param(config, :key, "value") - - assert config.params == %{key: "value"} - end - - test "updates existing param" do - config = Config.new( - method: :get, - url: "http://example.com", - params: %{key: "old"} - ) - - config = Config.put_param(config, :key, "new") - - assert config.params == %{key: "new"} - end - end -end diff --git a/test/hreq/cookies_test.exs b/test/hreq/cookies_test.exs deleted file mode 100644 index c5992a9..0000000 --- a/test/hreq/cookies_test.exs +++ /dev/null @@ -1,92 +0,0 @@ -defmodule HReq.CookiesTest do - use ExUnit.Case - alias HReq.Cookies - - describe "cookie parsing" do - test "parses basic cookie" do - cookie = Cookies.parse("name=value") - assert cookie.name == "name" - assert cookie.value == "value" - end - - test "parses cookie with attributes" do - cookie_str = "session=abc123; Domain=example.com; Path=/; HttpOnly; Secure" - cookie = Cookies.parse(cookie_str) - - assert cookie.name == "session" - assert cookie.value == "abc123" - assert cookie.domain == "example.com" - assert cookie.path == "/" - assert cookie.http_only == true - assert cookie.secure == true - end - end - - describe "cookie serialization" do - test "serializes cookie with attributes" do - cookie = %{ - name: "session", - value: "abc123", - domain: "example.com", - path: "/", - http_only: true, - secure: true - } - - serialized = Cookies.serialize(cookie) - assert serialized =~ "session=abc123" - assert serialized =~ "Domain=example.com" - assert serialized =~ "Path=/" - assert serialized =~ "HttpOnly" - assert serialized =~ "Secure" - end - end - - describe "cookie store" do - test "stores and retrieves cookies" do - store = Cookies.new_store() - cookie = %{ - name: "session", - value: "abc123", - domain: "example.com", - path: "/" - } - - store = Cookies.add_cookie(store, cookie) - cookies = Cookies.get_cookies(store, "example.com") - - assert length(cookies) == 1 - assert hd(cookies).name == "session" - end - - test "handles domain matching" do - store = Cookies.new_store() - cookie = %{ - name: "session", - value: "abc123", - domain: ".example.com", - path: "/" - } - - store = Cookies.add_cookie(store, cookie) - cookies = Cookies.get_cookies(store, "sub.example.com") - - assert length(cookies) == 1 - end - - test "handles path matching" do - store = Cookies.new_store() - cookie = %{ - name: "session", - value: "abc123", - domain: "example.com", - path: "/api" - } - - store = Cookies.add_cookie(store, cookie) - cookies = Cookies.get_cookies(store, "example.com", "/api/v1") - - assert length(cookies) == 1 - end - end -end diff --git a/test/hreq/error_test.exs b/test/hreq/error_test.exs deleted file mode 100644 index bb83dbc..0000000 --- a/test/hreq/error_test.exs +++ /dev/null @@ -1,47 +0,0 @@ -defmodule HReq.ErrorTest do - use ExUnit.Case - - alias HReq.Error - - describe "new/1" do - test "creates new error" do - error = Error.new(reason: :network_error, details: "connection refused") - - assert error.reason == :network_error - assert error.details == "connection refused" - assert is_list(error.stacktrace) - end - - test "accepts nil details" do - error = Error.new(reason: :timeout) - - assert error.reason == :timeout - assert error.details == nil - assert is_list(error.stacktrace) - end - end - - describe "message/1" do - test "formats error message with details" do - error = Error.new(reason: :http_error, details: "status 500") - assert Error.message(error) == "HReq.Error: http_error - status 500" - end - - test "formats error message without details" do - error = Error.new(reason: :timeout) - assert Error.message(error) == "HReq.Error: timeout" - end - - test "formats error message with map details" do - error = Error.new(reason: :validation_error, details: %{field: "invalid"}) - assert Error.message(error) == "HReq.Error: validation_error - %{field: \"invalid\"}" - end - end - - describe "Exception protocol" do - test "implements Exception protocol" do - error = Error.new(reason: :network_error, details: "connection refused") - assert Exception.message(error) == "HReq.Error: network_error - connection refused" - end - end -end diff --git a/test/hreq/logger_test.exs b/test/hreq/logger_test.exs deleted file mode 100644 index 568e81e..0000000 --- a/test/hreq/logger_test.exs +++ /dev/null @@ -1,143 +0,0 @@ -defmodule HReq.LoggerTest do - use ExUnit.Case - import ExUnit.CaptureLog - alias HReq.{Logger, Config, Response} - - describe "request logging" do - test "logs basic request/response" do - config = Config.new( - method: :get, - url: "http://example.com/api", - headers: [{"accept", "application/json"}] - ) - - response = %Response{ - status: 200, - body: ~s({"status": "ok"}), - headers: %{"content-type" => "application/json"} - } - - log = capture_log(fn -> - Logger.log(config, {:ok, response}, format: :basic) - end) - - assert log =~ "GET http://example.com/api" - assert log =~ "Response: 200" - end - - test "logs detailed request/response" do - config = Config.new( - method: :post, - url: "http://example.com/api", - headers: [{"content-type", "application/json"}], - body: %{key: "value"} - ) - - response = %Response{ - status: 201, - body: ~s({"id": 123}), - headers: %{"content-type" => "application/json"} - } - - log = capture_log(fn -> - Logger.log(config, {:ok, response}, format: :detailed) - end) - - assert log =~ "POST http://example.com/api" - assert log =~ "content-type: application/json" - assert log =~ ~s(%{key: "value"}) - assert log =~ "Status: 201" - assert log =~ ~s({"id": 123}) - end - - test "logs curl format" do - config = Config.new( - method: :post, - url: "http://example.com/api", - headers: [{"content-type", "application/json"}], - body: %{key: "value"} - ) - - log = capture_log(fn -> - Logger.log(config, {:ok, %Response{status: 200}}, format: :curl) - end) - - assert log =~ "curl -X POST" - assert log =~ "-H 'content-type: application/json'" - assert log =~ "'http://example.com/api'" - end - - test "logs errors" do - config = Config.new( - method: :get, - url: "http://example.com/api" - ) - - error = %HReq.Error{reason: :network_error} - - log = capture_log(fn -> - Logger.log(config, {:error, error}) - end) - - assert log =~ "Error:" - assert log =~ "network_error" - end - - test "supports custom format function" do - config = Config.new( - method: :get, - url: "http://example.com/api" - ) - - custom_format = fn config, response -> - "Custom: #{config.method} -> #{response.status}" - end - - log = capture_log(fn -> - Logger.log(config, {:ok, %Response{status: 200}}, format: custom_format) - end) - - assert log =~ "Custom: get -> 200" - end - end - - describe "curl formatting" do - test "formats simple requests" do - config = Config.new( - method: :get, - url: "http://example.com/api" - ) - - curl = Logger.as_curl(config) - assert curl == "curl -X GET 'http://example.com/api'" - end - - test "includes headers and body" do - config = Config.new( - method: :post, - url: "http://example.com/api", - headers: [{"content-type", "application/json"}], - body: %{key: "value"} - ) - - curl = Logger.as_curl(config) - assert curl =~ "curl -X POST" - assert curl =~ "-H 'content-type: application/json'" - assert curl =~ "-d" - assert curl =~ "%{key: \"value\"}" - end - - test "handles query parameters" do - config = Config.new( - method: :get, - url: "http://example.com/api", - params: %{q: "search", page: 1} - ) - - curl = Logger.as_curl(config) - assert curl =~ "curl -X GET" - assert curl =~ "q=search" - assert curl =~ "page=1" - end - end -end diff --git a/test/hreq/metrics/reporter_test.exs b/test/hreq/metrics/reporter_test.exs deleted file mode 100644 index 2f3fe51..0000000 --- a/test/hreq/metrics/reporter_test.exs +++ /dev/null @@ -1,61 +0,0 @@ -defmodule HReq.Metrics.ReporterTest do - use ExUnit.Case - - alias HReq.Metrics.Reporter - alias HReq.Config - - import ExUnit.CaptureLog - - describe "report/3" do - test "emits telemetry events" do - config = Config.new(method: :get, url: "http://example.com") - - :telemetry.attach( - "test-handler", - [:hreq, :request, :start], - fn name, measurements, metadata, _ -> - assert name == [:hreq, :request, :start] - assert is_map(measurements) - assert metadata.config == config - end, - nil - ) - - Reporter.report([:request, :start], %{}, %{config: config}) - - :telemetry.detach("test-handler") - end - - test "logs request start" do - config = Config.new(method: :get, url: "http://example.com") - - log = capture_log(fn -> - Reporter.report([:request, :start], %{}, %{config: config}) - end) - - assert log =~ "HReq request started: GET http://example.com" - end - - test "logs request completion" do - config = Config.new(method: :get, url: "http://example.com") - - log = capture_log(fn -> - Reporter.report([:request, :stop], %{duration: 123}, %{config: config}) - end) - - assert log =~ "HReq request completed in 123ms: GET http://example.com" - end - - test "logs request failure" do - config = Config.new(method: :get, url: "http://example.com") - error = RuntimeError.exception("test error") - - log = capture_log(fn -> - Reporter.report([:request, :exception], %{}, %{config: config, error: error}) - end) - - assert log =~ "HReq request failed: GET http://example.com" - assert log =~ inspect(error) - end - end -end diff --git a/test/hreq/metrics_test.exs b/test/hreq/metrics_test.exs deleted file mode 100644 index 6f76e2b..0000000 --- a/test/hreq/metrics_test.exs +++ /dev/null @@ -1,100 +0,0 @@ -defmodule HReq.MetricsTest do - use ExUnit.Case - alias HReq.{Metrics, Response, Error} - - defmodule TestReporter do - @behaviour HReq.Metrics.Reporter - - def start_link do - Agent.start_link(fn -> [] end) - end - - def get_metrics(pid) do - Agent.get(pid, & &1) - end - - @impl true - def report(metric) do - Agent.update(Process.get(:test_reporter), fn metrics -> - [metric | metrics] - end) - end - end - - setup do - {:ok, pid} = TestReporter.start_link() - Process.put(:test_reporter, pid) - start_supervised!(Metrics) - {:ok, reporter_pid: pid} - end - - describe "metrics collection" do - test "records successful requests", %{reporter_pid: pid} do - Metrics.add_reporter(TestReporter) - - response = %Response{status: 200, body: "success"} - Metrics.record(:get, "http://example.com", {:ok, response}, 100) - - [metric] = TestReporter.get_metrics(pid) - assert metric.method == :get - assert metric.url == "http://example.com" - assert metric.status == 200 - assert metric.duration == 100 - assert metric.bytes == 7 - assert metric.error == nil - end - - test "records failed requests", %{reporter_pid: pid} do - Metrics.add_reporter(TestReporter) - - error = Error.new(%{reason: :timeout}) - Metrics.record(:post, "http://example.com", {:error, error}, 500) - - [metric] = TestReporter.get_metrics(pid) - assert metric.method == :post - assert metric.status == nil - assert metric.duration == 500 - assert metric.error == error - end - - test "includes custom tags", %{reporter_pid: pid} do - Metrics.add_reporter(TestReporter) - - response = %Response{status: 200, body: ""} - - Metrics.record(:get, "http://example.com", {:ok, response}, 100, - tags: %{region: "us-east", env: "test"} - ) - - [metric] = TestReporter.get_metrics(pid) - assert metric.tags.region == "us-east" - assert metric.tags.env == "test" - end - end - - describe "metrics middleware" do - test "collects request metrics" do - config = - Config.new( - method: :get, - url: "http://example.com", - tags: %{test: true} - ) - - HReq.Middleware.Metrics.call(config, fn _config -> - # Simulate request - Process.sleep(10) - {:ok, %Response{status: 200, body: ""}} - end) - - metrics = Metrics.get_metrics() - assert length(metrics) == 1 - metric = hd(metrics) - assert metric.method == :get - assert metric.url == "http://example.com" - assert metric.status == 200 - assert metric.duration >= 10 - assert metric.tags.test == true - end - end -end diff --git a/test/hreq/middleware/auth_test.exs b/test/hreq/middleware/auth_test.exs deleted file mode 100644 index 44c7655..0000000 --- a/test/hreq/middleware/auth_test.exs +++ /dev/null @@ -1,47 +0,0 @@ -defmodule HReq.Middleware.AuthTest do - use ExUnit.Case - alias HReq.{Middleware.Auth, Config} - - describe "Auth middleware" do - test "skips authentication when no auth config" do - config = Config.new() - assert Auth.call(config, fn config -> {:ok, config} end) == {:ok, config} - end - - test "applies basic authentication" do - config = Config.new( - auth: %{ - type: :basic, - username: "user", - password: "pass" - } - ) - - assert {:ok, result} = Auth.call(config, fn config -> {:ok, config} end) - assert List.keyfind(result.headers, "authorization", 0) - end - - test "handles authentication errors" do - config = Config.new( - auth: %{ - type: :basic, - username: "user" - } - ) - - assert {:error, error} = Auth.call(config, fn config -> {:ok, config} end) - assert error.reason == :authentication_error - end - - test "handles unsupported auth type" do - config = Config.new( - auth: %{ - type: :unsupported - } - ) - - assert {:error, error} = Auth.call(config, fn config -> {:ok, config} end) - assert error.reason == :authentication_error - end - end -end diff --git a/test/hreq/middleware/cache_test.exs b/test/hreq/middleware/cache_test.exs deleted file mode 100644 index cbf166d..0000000 --- a/test/hreq/middleware/cache_test.exs +++ /dev/null @@ -1,68 +0,0 @@ -defmodule HReq.Middleware.CacheTest do - use ExUnit.Case - alias HReq.{Middleware.Cache, Config, Response} - - setup do - start_supervised!(HReq.Cache.Memory) - :ok - end - - describe "cache middleware" do - test "returns cached response when available" do - config = Config.new( - method: :get, - url: "http://example.com/api", - cache: %{backend: :memory} - ) - cached_response = %Response{status: 200, body: "cached data"} - - HReq.Cache.put(config, cached_response) - - assert {:ok, ^cached_response} = Cache.call(config, fn _ -> - {:ok, %Response{status: 200, body: "new data"}} - end) - end - - test "caches response when not in cache" do - config = Config.new( - method: :get, - url: "http://example.com/api", - cache: %{backend: :memory} - ) - new_response = %Response{status: 200, body: "new data"} - - assert {:ok, ^new_response} = Cache.call(config, fn _ -> - {:ok, new_response} - end) - - assert {:ok, ^new_response} = HReq.Cache.get(config) - end - - test "passes through errors" do - config = Config.new( - method: :get, - url: "http://example.com/api", - cache: %{backend: :memory} - ) - - assert {:error, _} = Cache.call(config, fn _ -> - {:error, %HReq.Error{reason: :network_error}} - end) - end - - test "skips caching for non-cacheable requests" do - config = Config.new( - method: :post, - url: "http://example.com/api", - cache: %{backend: :memory} - ) - response = %Response{status: 200, body: "data"} - - assert {:ok, ^response} = Cache.call(config, fn _ -> - {:ok, response} - end) - - assert {:error, _} = HReq.Cache.get(config) - end - end -end diff --git a/test/hreq/middleware/circuit_breaker_test.exs b/test/hreq/middleware/circuit_breaker_test.exs deleted file mode 100644 index 64f935c..0000000 --- a/test/hreq/middleware/circuit_breaker_test.exs +++ /dev/null @@ -1,44 +0,0 @@ -defmodule HReq.Middleware.CircuitBreakerTest do - use ExUnit.Case - alias HReq.{Middleware.CircuitBreaker, Config} - - describe "circuit breaker middleware" do - test "applies circuit breaker to requests" do - config = - Config.new( - base_url: "http://example.com", - circuit_breaker: [ - failure_threshold: 2, - reset_timeout: 100 - ] - ) - - # Successful request - assert {:ok, :success} = - CircuitBreaker.call(config, fn _ -> - {:ok, :success} - end) - - # Failed requests - CircuitBreaker.call(config, fn _ -> {:error, :test_error} end) - CircuitBreaker.call(config, fn _ -> {:error, :test_error} end) - - # Circuit should be open - assert {:error, error} = - CircuitBreaker.call(config, fn _ -> - {:ok, :success} - end) - - assert error.reason == :circuit_open - end - - test "skips circuit breaker when not configured" do - config = Config.new() - - assert {:ok, :success} = - CircuitBreaker.call(config, fn _ -> - {:ok, :success} - end) - end - end -end diff --git a/test/hreq/middleware/compression_test.exs b/test/hreq/middleware/compression_test.exs deleted file mode 100644 index b4481f7..0000000 --- a/test/hreq/middleware/compression_test.exs +++ /dev/null @@ -1,45 +0,0 @@ -defmodule HReq.Middleware.CompressionTest do - use ExUnit.Case - - alias HReq.Middleware.Compression - alias HReq.{Config, Response} - - describe "call/2" do - test "adds accept-encoding header and compresses request" do - config = %Config{ - method: :post, - url: "http://example.com", - body: "test data", - headers: [], - compression: true - } - - next = fn config -> - assert {"accept-encoding", "gzip, deflate"} in config.headers - assert {"content-encoding", "gzip"} in config.headers - assert config.body != "test data" - {:ok, %Response{status: 200, body: :zlib.gzip("response data"), headers: [{"content-encoding", "gzip"}]}} - end - - assert {:ok, response} = Compression.call(config, next) - assert response.body == "response data" - end - - test "skips compression when disabled" do - config = %Config{ - method: :get, - url: "http://example.com", - headers: [], - compression: false - } - - next = fn config -> - refute {"accept-encoding", "gzip, deflate"} in config.headers - {:ok, %Response{status: 200, body: "response data"}} - end - - assert {:ok, response} = Compression.call(config, next) - assert response.body == "response data" - end - end -end diff --git a/test/hreq/middleware/logger_test.exs b/test/hreq/middleware/logger_test.exs deleted file mode 100644 index 79d17f8..0000000 --- a/test/hreq/middleware/logger_test.exs +++ /dev/null @@ -1,63 +0,0 @@ -defmodule HReq.Middleware.LoggerTest do - use ExUnit.Case - import ExUnit.CaptureLog - alias HReq.{Middleware.Logger, Config} - - describe "logger middleware" do - test "logs requests when configured" do - config = - Config.new( - method: :get, - url: "http://example.com/api", - logger: [level: :info] - ) - - log = - capture_log(fn -> - Logger.call(config, fn _ -> - {:ok, %HReq.Response{status: 200}} - end) - end) - - assert log =~ "GET http://example.com/api" - assert log =~ "Response: 200" - end - - test "skips logging when not configured" do - config = - Config.new( - method: :get, - url: "http://example.com/api" - ) - - log = - capture_log(fn -> - Logger.call(config, fn _ -> - {:ok, %HReq.Response{status: 200}} - end) - end) - - assert log == "" - end - - test "logs errors" do - config = - Config.new( - method: :get, - url: "http://example.com/api", - logger: [level: :error] - ) - - log = - capture_log(fn -> - Logger.call(config, fn _ -> - {:error, %HReq.Error{reason: :network_error}} - end) - end) - - assert log =~ "GET http://example.com/api" - assert log =~ "Error:" - assert log =~ "network_error" - end - end -end diff --git a/test/hreq/middleware/oauth2_test.exs b/test/hreq/middleware/oauth2_test.exs deleted file mode 100644 index fa19b7a..0000000 --- a/test/hreq/middleware/oauth2_test.exs +++ /dev/null @@ -1,80 +0,0 @@ -defmodule HReq.Middleware.OAuth2Test do - use ExUnit.Case - - alias HReq.Middleware.OAuth2 - alias HReq.{Config, Response} - alias HReq.OAuth2, as: OAuth2Module - - describe "call/2" do - test "adds authorization header with valid token" do - oauth = OAuth2Module.new( - token: "valid_token", - expires_at: System.system_time(:second) + 3600 - ) - - config = %Config{ - method: :get, - url: "http://example.com", - headers: [], - oauth2: oauth - } - - next = fn config -> - assert {"authorization", "Bearer valid_token"} in config.headers - {:ok, %Response{status: 200, body: "success"}} - end - - assert {:ok, %Response{status: 200, body: "success"}} = OAuth2.call(config, next) - end - - test "refreshes expired token" do - oauth = OAuth2Module.new( - client_id: "client_id", - client_secret: "client_secret", - token_url: "http://example.com/token", - token: "expired_token", - refresh_token: "refresh_token", - expires_at: System.system_time(:second) - 1 - ) - - config = %Config{ - method: :get, - url: "http://example.com", - headers: [], - oauth2: oauth - } - - next = fn config -> - assert {"authorization", "Bearer new_token"} in config.headers - {:ok, %Response{status: 200, body: "success"}} - end - - mock_refresh = fn _ -> - {:ok, %{oauth | token: "new_token", expires_at: System.system_time(:second) + 3600}} - end - - original_refresh = &OAuth2Module.refresh/1 - :meck.new(OAuth2Module, [:passthrough]) - :meck.expect(OAuth2Module, :refresh, mock_refresh) - - assert {:ok, %Response{status: 200, body: "success"}} = OAuth2.call(config, next) - - :meck.unload(OAuth2Module) - end - - test "skips OAuth2 when not configured" do - config = %Config{ - method: :get, - url: "http://example.com", - headers: [] - } - - next = fn config -> - refute List.keymember?(config.headers, "authorization", 0) - {:ok, %Response{status: 200, body: "success"}} - end - - assert {:ok, %Response{status: 200, body: "success"}} = OAuth2.call(config, next) - end - end -end diff --git a/test/hreq/middleware/pipeline_test.exs b/test/hreq/middleware/pipeline_test.exs deleted file mode 100644 index 67a6aad..0000000 --- a/test/hreq/middleware/pipeline_test.exs +++ /dev/null @@ -1,92 +0,0 @@ -defmodule HReq.Middleware.PipelineTest do - use ExUnit.Case - - alias HReq.Middleware.Pipeline - alias HReq.{Config, Response, Error} - - defmodule TestMiddleware do - @behaviour HReq.Middleware - - def call(config, next) do - config = update_in(config.headers, &[{"x-test", "true"} | &1]) - next.(config) - end - end - - defmodule ErrorMiddleware do - @behaviour HReq.Middleware - - def call(_config, _next) do - raise "Middleware error" - end - end - - describe "run/3" do - test "executes middleware pipeline" do - config = %Config{ - method: :get, - url: "http://example.com", - headers: [] - } - - middleware = [TestMiddleware] - - request_fun = fn config -> - assert {"x-test", "true"} in config.headers - {:ok, %Response{status: 200, body: "success"}} - end - - assert {:ok, %Response{status: 200, body: "success"}} = - Pipeline.run(config, middleware, request_fun) - end - - test "handles middleware errors" do - config = %Config{ - method: :get, - url: "http://example.com", - headers: [] - } - - middleware = [ErrorMiddleware] - request_fun = fn _config -> {:ok, %Response{}} end - - assert {:error, %Error{reason: :middleware_error}} = - Pipeline.run(config, middleware, request_fun) - end - - test "emits telemetry events" do - config = %Config{ - method: :get, - url: "http://example.com" - } - - :telemetry.attach( - "test-handler", - [:hreq, :request, :start], - fn name, measurements, metadata, _ -> - assert name == [:hreq, :request, :start] - assert is_map(measurements) - assert metadata.config == config - end, - nil - ) - - :telemetry.attach( - "test-handler-stop", - [:hreq, :request, :stop], - fn name, measurements, metadata, _ -> - assert name == [:hreq, :request, :stop] - assert is_map(measurements) - assert is_map(metadata.result) - assert metadata.config == config - end, - nil - ) - - Pipeline.run(config, [], fn _ -> {:ok, %Response{status: 200}} end) - - :telemetry.detach("test-handler") - :telemetry.detach("test-handler-stop") - end - end -end diff --git a/test/hreq/middleware/prepare_request_test.exs b/test/hreq/middleware/prepare_request_test.exs deleted file mode 100644 index 7913d37..0000000 --- a/test/hreq/middleware/prepare_request_test.exs +++ /dev/null @@ -1,92 +0,0 @@ -defmodule HReq.Middleware.PrepareRequestTest do - use ExUnit.Case - - alias HReq.Middleware.PrepareRequest - alias HReq.{Config, Response} - - describe "call/2" do - test "prepares URL with query params" do - config = %Config{ - method: :get, - url: "http://example.com/test", - params: %{key: "value", other: "param"}, - headers: [] - } - - next = fn config -> - assert config.url == "http://example.com/test?key=value&other=param" - {:ok, %Response{status: 200}} - end - - assert {:ok, %Response{status: 200}} = PrepareRequest.call(config, next) - end - - test "merges URL query params with config params" do - config = %Config{ - method: :get, - url: "http://example.com/test?existing=param", - params: %{key: "value"}, - headers: [] - } - - next = fn config -> - assert config.url == "http://example.com/test?existing=param&key=value" - {:ok, %Response{status: 200}} - end - - assert {:ok, %Response{status: 200}} = PrepareRequest.call(config, next) - end - - test "normalizes headers" do - config = %Config{ - method: :get, - url: "http://example.com", - headers: [ - {"Content-Type", "application/json"}, - {"ACCEPT", "application/json"} - ] - } - - next = fn config -> - assert {"content-type", "application/json"} in config.headers - assert {"accept", "application/json"} in config.headers - {:ok, %Response{status: 200}} - end - - assert {:ok, %Response{status: 200}} = PrepareRequest.call(config, next) - end - - test "prepares JSON body" do - config = %Config{ - method: :post, - url: "http://example.com", - headers: [], - body: %{data: "test"} - } - - next = fn config -> - assert config.body == ~s({"data":"test"}) - assert {"content-type", "application/json"} in config.headers - {:ok, %Response{status: 200}} - end - - assert {:ok, %Response{status: 200}} = PrepareRequest.call(config, next) - end - - test "adds content-length for binary body" do - config = %Config{ - method: :post, - url: "http://example.com", - headers: [], - body: "test data" - } - - next = fn config -> - assert {"content-length", "9"} in config.headers - {:ok, %Response{status: 200}} - end - - assert {:ok, %Response{status: 200}} = PrepareRequest.call(config, next) - end - end -end diff --git a/test/hreq/middleware/proxy_rotation_test.exs b/test/hreq/middleware/proxy_rotation_test.exs deleted file mode 100644 index a854574..0000000 --- a/test/hreq/middleware/proxy_rotation_test.exs +++ /dev/null @@ -1,56 +0,0 @@ -defmodule HReq.Middleware.ProxyRotationTest do - use ExUnit.Case - alias HReq.{Middleware.ProxyRotation, Config, Proxy} - - setup do - start_supervised!(Proxy) - :ok - end - - describe "proxy rotation middleware" do - test "applies proxy to request" do - proxy = %{ - url: "http://proxy.example.com:8080", - protocol: "http", - username: "user", - password: "pass" - } - - :ok = Proxy.add_proxy(proxy) - - config = Config.new(proxy_rotation: true) - - ProxyRotation.call(config, fn config -> - assert config.proxy == "http://user:pass@proxy.example.com:8080" - {:ok, :success} - end) - end - - test "handles proxy failures" do - proxy = %{ - url: "http://proxy.example.com:8080", - protocol: "http" - } - - :ok = Proxy.add_proxy(proxy) - - config = Config.new(proxy_rotation: true) - - result = ProxyRotation.call(config, fn _config -> - {:error, %HReq.Error{reason: :network_error}} - end) - - assert {:error, _} = result - assert {:error, :no_proxies_available} = Proxy.get_proxy() - end - - test "skips proxy rotation when disabled" do - config = Config.new(proxy_rotation: false) - - ProxyRotation.call(config, fn config -> - assert config.proxy == nil - {:ok, :success} - end) - end - end -end diff --git a/test/hreq/middleware/rate_limit_test.exs b/test/hreq/middleware/rate_limit_test.exs deleted file mode 100644 index 6f057ff..0000000 --- a/test/hreq/middleware/rate_limit_test.exs +++ /dev/null @@ -1,56 +0,0 @@ -defmodule HReq.Middleware.RateLimitTest do - use ExUnit.Case - alias HReq.{Middleware.RateLimit, Config} - - describe "rate limit middleware" do - test "applies rate limiting to requests" do - config = - Config.new( - base_url: "http://example.com", - rate_limit: [ - rate: 10.0, - burst: 2 - ] - ) - - # First requests should succeed - assert {:ok, :success} = RateLimit.call(config, fn _ -> {:ok, :success} end) - assert {:ok, :success} = RateLimit.call(config, fn _ -> {:ok, :success} end) - - # Third request should be rate limited - assert {:error, error} = RateLimit.call(config, fn _ -> {:ok, :success} end) - assert error.reason == :rate_limited - end - - test "skips rate limiting when not configured" do - config = Config.new() - - assert {:ok, :success} = - RateLimit.call(config, fn _ -> - {:ok, :success} - end) - end - - test "uses separate limiters for different URLs" do - config1 = - Config.new( - base_url: "http://api1.example.com", - rate_limit: [rate: 10.0, burst: 1] - ) - - config2 = - Config.new( - base_url: "http://api2.example.com", - rate_limit: [rate: 10.0, burst: 1] - ) - - # Both first requests should succeed - assert {:ok, :success} = RateLimit.call(config1, fn _ -> {:ok, :success} end) - assert {:ok, :success} = RateLimit.call(config2, fn _ -> {:ok, :success} end) - - # Second requests should be rate limited - assert {:error, _} = RateLimit.call(config1, fn _ -> {:ok, :success} end) - assert {:error, _} = RateLimit.call(config2, fn _ -> {:ok, :success} end) - end - end -end diff --git a/test/hreq/middleware/rate_limiter_test.exs b/test/hreq/middleware/rate_limiter_test.exs deleted file mode 100644 index 1eabd43..0000000 --- a/test/hreq/middleware/rate_limiter_test.exs +++ /dev/null @@ -1,52 +0,0 @@ -defmodule HReq.Middleware.RateLimiterTest do - use ExUnit.Case, async: true - alias HReq.Middleware.RateLimiter - alias HReq.Config - - setup do - config = Config.new( - base_url: "https://api.example.com", - rate_limit: 5, - rate_interval: 1000 - ) - {:ok, config: config} - end - - test "allows requests within rate limit", %{config: config} do - for _ <- 1..5 do - assert {:ok, :success} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - end - end - - test "blocks requests exceeding rate limit", %{config: config} do - for _ <- 1..5 do - assert {:ok, :success} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - end - - assert {:error, error} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - assert error.reason == :rate_limited - assert is_integer(error.wait_time) - end - - test "resets rate limit after interval", %{config: config} do - for _ <- 1..5 do - assert {:ok, :success} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - end - - :timer.sleep(1000) - - assert {:ok, :success} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - end - - test "handles multiple base URLs separately", %{config: config} do - config2 = %{config | base_url: "https://api2.example.com"} - - for _ <- 1..5 do - assert {:ok, :success} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - assert {:ok, :success} = RateLimiter.call(config2, fn _ -> {:ok, :success} end) - end - - assert {:error, _} = RateLimiter.call(config, fn _ -> {:ok, :success} end) - assert {:error, _} = RateLimiter.call(config2, fn _ -> {:ok, :success} end) - end -end diff --git a/test/hreq/middleware/retry_test.exs b/test/hreq/middleware/retry_test.exs deleted file mode 100644 index 22eec3c..0000000 --- a/test/hreq/middleware/retry_test.exs +++ /dev/null @@ -1,61 +0,0 @@ -defmodule HReq.Middleware.RetryTest do - use ExUnit.Case - - alias HReq.Middleware.Retry - alias HReq.{Config, Response, Error} - - describe "call/2" do - test "retries failed requests" do - config = %Config{ - method: :get, - url: "http://example.com", - retry: %{ - max_attempts: 3, - delay: 10, - jitter: false - } - } - - attempts = 0 - - next = fn _config -> - attempts = attempts + 1 - - if attempts < 3 do - {:error, Error.new(reason: :network_error)} - else - {:ok, %Response{status: 200, body: "success"}} - end - end - - assert {:ok, %Response{status: 200, body: "success"}} = Retry.call(config, next) - end - - test "respects max attempts" do - config = %Config{ - method: :get, - url: "http://example.com", - retry: %{ - max_attempts: 2, - delay: 10, - jitter: false - } - } - - next = fn _config -> {:error, Error.new(reason: :network_error)} end - - assert {:error, %Error{reason: :network_error}} = Retry.call(config, next) - end - - test "skips retry when not configured" do - config = %Config{ - method: :get, - url: "http://example.com" - } - - next = fn _config -> {:error, Error.new(reason: :network_error)} end - - assert {:error, %Error{reason: :network_error}} = Retry.call(config, next) - end - end -end diff --git a/test/hreq/middleware/signing_test.exs b/test/hreq/middleware/signing_test.exs deleted file mode 100644 index 219f08c..0000000 --- a/test/hreq/middleware/signing_test.exs +++ /dev/null @@ -1,45 +0,0 @@ -defmodule HReq.Middleware.SigningTest do - use ExUnit.Case - - alias HReq.Middleware.Signing - alias HReq.{Config, Response} - - describe "call/2" do - test "signs request" do - config = %Config{ - method: :get, - url: "http://example.com/test", - headers: [{"content-type", "application/json"}], - body: ~s({"data": "test"}), - signing: %{ - algorithm: :hmac_sha256, - provider: :generic, - credentials: %{secret_key: "test_secret"} - } - } - - next = fn config -> - assert {"authorization", "Signature " <> _} = List.keyfind(config.headers, "authorization", 0) - {:ok, %Response{status: 200, body: "success"}} - end - - assert {:ok, %Response{status: 200, body: "success"}} = Signing.call(config, next) - end - - test "skips signing when not configured" do - config = %Config{ - method: :get, - url: "http://example.com/test", - headers: [{"content-type", "application/json"}], - body: ~s({"data": "test"}) - } - - next = fn config -> - refute List.keymember?(config.headers, "authorization", 0) - {:ok, %Response{status: 200, body: "success"}} - end - - assert {:ok, %Response{status: 200, body: "success"}} = Signing.call(config, next) - end - end -end diff --git a/test/hreq/middleware/validator_test.exs b/test/hreq/middleware/validator_test.exs deleted file mode 100644 index ad8c291..0000000 --- a/test/hreq/middleware/validator_test.exs +++ /dev/null @@ -1,67 +0,0 @@ -defmodule HReq.Middleware.ValidatorTest do - use ExUnit.Case - - alias HReq.Middleware.Validator - alias HReq.{Config, Response, Error} - - describe "call/2" do - test "validates successful response" do - config = %Config{ - method: :get, - url: "http://example.com", - validator: %{ - status: [200..299], - headers: %{"content-type" => ~r/application\/json/}, - body: fn body -> String.contains?(body, "success") end - } - } - - next = fn _config -> - {:ok, - %Response{ - status: 200, - headers: [{"content-type", "application/json"}], - body: ~s({"message": "success"}) - }} - end - - assert {:ok, %Response{status: 200}} = Validator.call(config, next) - end - - test "returns error for invalid response" do - config = %Config{ - method: :get, - url: "http://example.com", - validator: %{ - status: [200..299], - headers: %{"content-type" => "application/json"}, - body: fn body -> String.contains?(body, "success") end - } - } - - next = fn _config -> - {:ok, - %Response{ - status: 200, - headers: [{"content-type", "text/plain"}], - body: "failure" - }} - end - - assert {:error, %Error{reason: :invalid_header}} = Validator.call(config, next) - end - - test "skips validation when not configured" do - config = %Config{ - method: :get, - url: "http://example.com" - } - - next = fn _config -> - {:ok, %Response{status: 200, body: "success"}} - end - - assert {:ok, %Response{status: 200, body: "success"}} = Validator.call(config, next) - end - end -end diff --git a/test/hreq/middleware_test.exs b/test/hreq/middleware_test.exs deleted file mode 100644 index 4e109d1..0000000 --- a/test/hreq/middleware_test.exs +++ /dev/null @@ -1,56 +0,0 @@ -defmodule HReq.MiddlewareTest do - use ExUnit.Case - - alias HReq.{Middleware, Config} - - describe "Retry middleware" do - test "retries failed requests" do - config = Config.new(retry: 2) - - attempt = fn -> - Process.put(:attempts, (Process.get(:attempts) || 0) + 1) - - case Process.get(:attempts) do - 3 -> {:ok, %HReq.Response{status: 200}} - _ -> {:error, %HReq.Error{reason: :network_error}} - end - end - - result = Middleware.Retry.call(config, fn _ -> attempt.() end) - - assert {:ok, response} = result - assert response.status == 200 - assert Process.get(:attempts) == 3 - end - end - - describe "Logger middleware" do - import ExUnit.CaptureLog - - test "logs successful requests" do - config = Config.new() - - log = - capture_log(fn -> - Middleware.Logger.call(config, fn _ -> - {:ok, %HReq.Response{status: 200}} - end) - end) - - assert log =~ "Request completed: 200" - end - - test "logs failed requests" do - config = Config.new() - - log = - capture_log(fn -> - Middleware.Logger.call(config, fn _ -> - {:error, %HReq.Error{reason: :network_error}} - end) - end) - - assert log =~ "Request failed: network_error" - end - end -end diff --git a/test/hreq/oauth2_test.exs b/test/hreq/oauth2_test.exs deleted file mode 100644 index b203e03..0000000 --- a/test/hreq/oauth2_test.exs +++ /dev/null @@ -1,82 +0,0 @@ -defmodule HReq.OAuth2Test do - use ExUnit.Case - - alias HReq.OAuth2 - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass, url: "http://localhost:#{bypass.port}"} - end - - describe "get_token/3" do - test "gets client credentials token", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "POST", "/token", fn conn -> - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, ~s({ - "access_token": "test_token", - "token_type": "bearer", - "expires_in": 3600 - })) - end) - - oauth = - OAuth2.new( - client_id: "client_id", - client_secret: "client_secret", - token_url: "#{url}/token" - ) - - assert {:ok, oauth} = OAuth2.get_token(oauth, :client_credentials) - assert oauth.token == "test_token" - assert oauth.expires_at > System.system_time(:second) - end - end - - describe "refresh/1" do - test "refreshes token", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "POST", "/token", fn conn -> - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, ~s({ - "access_token": "new_token", - "token_type": "bearer", - "expires_in": 3600 - })) - end) - - oauth = - OAuth2.new( - client_id: "client_id", - client_secret: "client_secret", - token_url: "#{url}/token", - refresh_token: "refresh_token" - ) - - assert {:ok, oauth} = OAuth2.refresh(oauth) - assert oauth.token == "new_token" - end - end - - describe "expired?/1" do - test "returns true for expired token" do - oauth = - OAuth2.new( - token: "test_token", - expires_at: System.system_time(:second) - 1 - ) - - assert OAuth2.expired?(oauth) - end - - test "returns false for valid token" do - oauth = - OAuth2.new( - token: "test_token", - expires_at: System.system_time(:second) + 3600 - ) - - refute OAuth2.expired?(oauth) - end - end -end diff --git a/test/hreq/pipeline_test.exs b/test/hreq/pipeline_test.exs deleted file mode 100644 index ec3aed1..0000000 --- a/test/hreq/pipeline_test.exs +++ /dev/null @@ -1,76 +0,0 @@ -defmodule HReq.PipelineTest do - use ExUnit.Case - alias HReq.{Pipeline, Config, Response, Error} - - describe "pipeline processing" do - test "processes successful request through hooks" do - pipeline = Pipeline.new( - request_hooks: [ - fn config -> {:ok, %{config | headers: [{"x-test", "true"} | config.headers]}} end - ], - response_hooks: [ - fn response -> {:ok, %{response | body: String.upcase(response.body)}} end - ] - ) - - config = Config.new(pipeline: pipeline) - - result = Pipeline.process(config, fn config -> - assert {"x-test", "true"} in config.headers - {:ok, %Response{status: 200, body: "hello"}} - end) - - assert {:ok, response} = result - assert response.body == "HELLO" - end - - test "handles hook errors" do - pipeline = Pipeline.new( - request_hooks: [ - fn _config -> {:error, Error.new(%{reason: :validation_error})} end - ] - ) - - config = Config.new(pipeline: pipeline) - - result = Pipeline.process(config, fn _config -> - {:ok, %Response{status: 200}} - end) - - assert {:error, error} = result - assert error.reason == :validation_error - end - end - - describe "built-in hooks" do - test "validate_status hook" do - hook = Pipeline.validate_status([200..299]) - - assert {:ok, _} = hook.(%Response{status: 200}) - assert {:error, error} = hook.(%Response{status: 404}) - assert error.reason == :invalid_status - end - - test "transform_body hook" do - hook = Pipeline.transform_body([ - "application/json": fn body -> Jason.decode(body) end - ]) - - response = %Response{ - body: ~s({"key": "value"}), - headers: %{"content-type" => "application/json"} - } - - assert {:ok, transformed} = hook.(response) - assert transformed.body == %{"key" => "value"} - end - - test "add_headers hook" do - hook = Pipeline.add_headers([{"user-agent", "test"}]) - config = Config.new() - - assert {:ok, new_config} = hook.(config) - assert {"user-agent", "test"} in new_config.headers - end - end -end diff --git a/test/hreq/proxy_test.exs b/test/hreq/proxy_test.exs deleted file mode 100644 index d4f2b45..0000000 --- a/test/hreq/proxy_test.exs +++ /dev/null @@ -1,65 +0,0 @@ -defmodule HReq.ProxyTest do - use ExUnit.Case - alias HReq.Proxy - - setup do - start_supervised!({Proxy, [name: :test_proxy_manager]}) - {:ok, proxy_manager: :test_proxy_manager} - end - - describe "proxy management" do - test "adds and retrieves proxy", %{proxy_manager: manager} do - proxy = %{ - url: "http://proxy1.example.com:8080", - protocol: "http", - username: "user", - password: "pass" - } - - :ok = Proxy.add_proxy(proxy, manager) - assert {:ok, retrieved} = Proxy.get_proxy(manager) - assert retrieved.url == proxy.url - end - - test "removes proxy", %{proxy_manager: manager} do - proxy = %{url: "http://proxy1.example.com:8080", protocol: "http"} - :ok = Proxy.add_proxy(proxy, manager) - :ok = Proxy.remove_proxy(proxy.url, manager) - assert {:error, :no_proxies_available} = Proxy.get_proxy(manager) - end - - test "handles proxy failure reporting", %{proxy_manager: manager} do - proxy = %{url: "http://proxy1.example.com:8080", protocol: "http"} - :ok = Proxy.add_proxy(proxy, manager) - - # Report three failures - for _ <- 1..3 do - Proxy.report_proxy_status(proxy.url, false, manager) - end - - assert {:error, :no_proxies_available} = Proxy.get_proxy(manager) - end - - test "rotates proxies with round robin strategy", %{proxy_manager: manager} do - proxies = [ - %{url: "http://proxy1.example.com:8080", protocol: "http"}, - %{url: "http://proxy2.example.com:8080", protocol: "http"}, - %{url: "http://proxy3.example.com:8080", protocol: "http"} - ] - - for proxy <- proxies do - :ok = Proxy.add_proxy(proxy, manager) - end - - {:ok, first} = Proxy.get_proxy(manager) - {:ok, second} = Proxy.get_proxy(manager) - {:ok, third} = Proxy.get_proxy(manager) - {:ok, fourth} = Proxy.get_proxy(manager) - - assert first.url == "http://proxy1.example.com:8080" - assert second.url == "http://proxy2.example.com:8080" - assert third.url == "http://proxy3.example.com:8080" - assert fourth.url == "http://proxy1.example.com:8080" - end - end -end diff --git a/test/hreq/rate_limit_test.exs b/test/hreq/rate_limit_test.exs deleted file mode 100644 index 6ed61dc..0000000 --- a/test/hreq/rate_limit_test.exs +++ /dev/null @@ -1,62 +0,0 @@ -defmodule HReq.RateLimitTest do - use ExUnit.Case - alias HReq.RateLimit - - setup do - {:ok, _pid} = RateLimit.start_link( - name: :test_limiter, - rate: 10.0, - burst: 5 - ) - {:ok, limiter: :test_limiter} - end - - describe "rate limiting" do - test "allows requests within rate limit", %{limiter: limiter} do - assert :ok = RateLimit.take(limiter) - assert :ok = RateLimit.take(limiter) - assert :ok = RateLimit.take(limiter) - assert RateLimit.tokens_available(limiter) == 2 - end - - test "blocks requests when limit exceeded", %{limiter: limiter} do - # Use all tokens - for _ <- 1..5 do - assert :ok = RateLimit.take(limiter) - end - - # Next request should be rate limited - assert {:error, error} = RateLimit.take(limiter) - assert error.reason == :rate_limited - assert error.wait_time > 0 - end - - test "replenishes tokens over time", %{limiter: limiter} do - # Use some tokens - assert :ok = RateLimit.take(limiter, 3) - assert RateLimit.tokens_available(limiter) == 2 - - # Wait for token replenishment - Process.sleep(200) # Wait for 2 tokens at 10/sec - - available = RateLimit.tokens_available(limiter) - assert available >= 3 - end - - test "respects burst limit", %{limiter: limiter} do - # Try to take more than burst - assert {:error, error} = RateLimit.take(limiter, 6) - assert error.reason == :rate_limited - end - - test "resets rate limiter", %{limiter: limiter} do - # Use some tokens - assert :ok = RateLimit.take(limiter, 3) - assert RateLimit.tokens_available(limiter) == 2 - - # Reset - assert :ok = RateLimit.reset(limiter) - assert RateLimit.tokens_available(limiter) == 5 - end - end -end diff --git a/test/hreq/response_test.exs b/test/hreq/response_test.exs deleted file mode 100644 index f674106..0000000 --- a/test/hreq/response_test.exs +++ /dev/null @@ -1,74 +0,0 @@ -defmodule HReq.ResponseTest do - use ExUnit.Case - - alias HReq.Response - - describe "new/1" do - test "creates new response" do - response = Response.new( - status: 200, - headers: [{"content-type", "application/json"}], - body: ~s({"data": "test"}), - request_time: 100 - ) - - assert response.status == 200 - assert response.headers == [{"content-type", "application/json"}] - assert response.body == ~s({"data": "test"}) - assert response.request_time == 100 - end - end - - describe "successful?/1" do - test "returns true for 2xx status codes" do - assert Response.new(status: 200) |> Response.successful?() - assert Response.new(status: 201) |> Response.successful?() - assert Response.new(status: 204) |> Response.successful?() - refute Response.new(status: 404) |> Response.successful?() - refute Response.new(status: 500) |> Response.successful?() - end - end - - describe "get_header/3" do - test "returns header value case-insensitively" do - response = Response.new( - status: 200, - headers: [{"Content-Type", "application/json"}] - ) - - assert Response.get_header(response, "content-type") == "application/json" - assert Response.get_header(response, "CONTENT-TYPE") == "application/json" - assert Response.get_header(response, "missing") == nil - assert Response.get_header(response, "missing", "default") == "default" - end - end - - describe "json/1" do - test "parses JSON body" do - response = Response.new( - status: 200, - body: ~s({"data": "test"}) - ) - - assert {:ok, %{"data" => "test"}} = Response.json(response) - end - - test "returns error for invalid JSON" do - response = Response.new( - status: 200, - body: "invalid json" - ) - - assert {:error, %Jason.DecodeError{}} = Response.json(response) - end - - test "handles pre-decoded JSON body" do - response = Response.new( - status: 200, - body: %{"data" => "test"} - ) - - assert {:ok, %{"data" => "test"}} = Response.json(response) - end - end -end diff --git a/test/hreq/retry_test.exs b/test/hreq/retry_test.exs deleted file mode 100644 index b5f385b..0000000 --- a/test/hreq/retry_test.exs +++ /dev/null @@ -1,71 +0,0 @@ -defmodule HReq.RetryTest do - use ExUnit.Case - - alias HReq.{Retry, Config, Response, Error} - - describe "run/2" do - test "retries failed requests" do - config = %Config{ - retry: %{ - max_attempts: 3, - delay: 10, - jitter: false - } - } - - attempts = 0 - - fun = fn -> - attempts = attempts + 1 - if attempts < 3 do - {:error, Error.new(reason: :network_error)} - else - {:ok, %Response{status: 200}} - end - end - - assert {:ok, %Response{status: 200}} = Retry.run(config, fun) - end - - test "respects max attempts" do - config = %Config{ - retry: %{ - max_attempts: 2, - delay: 10, - jitter: false - } - } - - fun = fn -> {:error, Error.new(reason: :network_error)} end - - assert {:error, %Error{reason: :network_error}} = Retry.run(config, fun) - end - - test "uses custom retry_if function" do - config = %Config{ - retry: %{ - max_attempts: 3, - delay: 10, - jitter: false, - retry_if: fn - %Response{status: 418} -> true - _ -> false - end - } - } - - attempts = 0 - - fun = fn -> - attempts = attempts + 1 - if attempts < 3 do - {:ok, %Response{status: 418}} - else - {:ok, %Response{status: 200}} - end - end - - assert {:ok, %Response{status: 200}} = Retry.run(config, fun) - end - end -end diff --git a/test/hreq/session_store_test.exs b/test/hreq/session_store_test.exs deleted file mode 100644 index a73f564..0000000 --- a/test/hreq/session_store_test.exs +++ /dev/null @@ -1,52 +0,0 @@ -defmodule HReq.SessionStoreTest do - use ExUnit.Case - alias HReq.SessionStore - - setup do - start_supervised!(SessionStore) - :ok - end - - describe "session management" do - test "creates new session" do - assert {:ok, session_id} = SessionStore.create_session() - assert is_binary(session_id) - end - - test "retrieves session" do - {:ok, session_id} = SessionStore.create_session() - assert {:ok, session} = SessionStore.get_session(session_id) - assert session.id == session_id - assert %DateTime{} = session.created_at - end - - test "updates session data" do - {:ok, session_id} = SessionStore.create_session() - assert :ok = SessionStore.update_session(session_id, %{user_id: 123}) - - {:ok, session} = SessionStore.get_session(session_id) - assert session.data.user_id == 123 - end - - test "deletes session" do - {:ok, session_id} = SessionStore.create_session() - assert :ok = SessionStore.delete_session(session_id) - assert {:error, :not_found} = SessionStore.get_session(session_id) - end - end - - describe "cookie management" do - test "adds and retrieves cookies" do - {:ok, session_id} = SessionStore.create_session() - - cookies = [ - %{name: "session", value: "abc123", domain: "example.com", path: "/"} - ] - - assert :ok = SessionStore.add_cookies(session_id, cookies) - assert {:ok, stored_cookies} = SessionStore.get_cookies(session_id, "example.com") - assert length(stored_cookies) == 1 - assert hd(stored_cookies).name == "session" - end - end -end diff --git a/test/hreq/session_test.exs b/test/hreq/session_test.exs deleted file mode 100644 index 17be48d..0000000 --- a/test/hreq/session_test.exs +++ /dev/null @@ -1,55 +0,0 @@ -defmodule HReq.SessionTest do - use ExUnit.Case - - alias HReq.Session - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass} - end - - describe "session management" do - test "maintains cookies across requests", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/first", fn conn -> - conn - |> Plug.Conn.put_resp_header("set-cookie", "session=123") - |> Plug.Conn.resp(200, "first") - end) - - Bypass.expect(bypass, "GET", "/second", fn conn -> - assert Plug.Conn.get_req_header(conn, "cookie") == ["session=123"] - Plug.Conn.resp(conn, 200, "second") - end) - - session = Session.new() - url1 = "http://localhost:#{bypass.port}/first" - url2 = "http://localhost:#{bypass.port}/second" - - {result1, session} = Session.request(session, :get, url1) - assert {:ok, response1} = result1 - assert response1.status == 200 - - {result2, _session} = Session.request(session, :get, url2) - assert {:ok, response2} = result2 - assert response2.status == 200 - end - - test "handles browser mode session", %{bypass: bypass} do - Bypass.expect(bypass, "GET", "/", fn conn -> - conn - |> Plug.Conn.put_resp_content_type("text/html") - |> Plug.Conn.resp(200, "<html><body>Hello</body></html>") - end) - - session = Session.new(browser_mode: true) - url = "http://localhost:#{bypass.port}/" - - {result, session} = Session.request(session, :get, url) - assert {:ok, response} = result - assert response.status == 200 - assert response.body =~ "Hello" - - :ok = Session.close(session) - end - end -end diff --git a/test/hreq/signing_test.exs b/test/hreq/signing_test.exs deleted file mode 100644 index f7d1726..0000000 --- a/test/hreq/signing_test.exs +++ /dev/null @@ -1,71 +0,0 @@ -defmodule HReq.SigningTest do - use ExUnit.Case - - alias HReq.{Signing, Config} - - describe "sign/4" do - test "signs request with HMAC-SHA256" do - config = %Config{ - method: :get, - url: "http://example.com/test", - headers: [{"content-type", "application/json"}], - body: ~s({"data": "test"}) - } - - {:ok, signed_config} = Signing.sign(config, :hmac_sha256, :generic, %{secret_key: "test_secret"}) - - assert {"authorization", "Signature " <> signature} = - List.keyfind(signed_config.headers, "authorization", 0) - - assert is_binary(signature) - end - - test "signs request for AWS" do - config = %Config{ - method: :get, - url: "https://s3.amazonaws.com/test-bucket/object.txt", - headers: [{"host", "s3.amazonaws.com"}] - } - - credentials = %{ - access_key: "AKIAIOSFODNN7EXAMPLE", - secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - region: "us-east-1" - } - - {:ok, signed_config} = Signing.sign(config, :hmac_sha256, :aws, credentials) - - assert {"authorization", "AWS4-HMAC-SHA256 " <> _} = - List.keyfind(signed_config.headers, "authorization", 0) - - assert {"x-amz-date", _} = List.keyfind(signed_config.headers, "x-amz-date", 0) - end - end - - describe "verify/4" do - test "verifies valid signature" do - config = %Config{ - method: :get, - url: "http://example.com/test", - headers: [{"content-type", "application/json"}], - body: ~s({"data": "test"}) - } - - {:ok, signed_config} = Signing.sign(config, :hmac_sha256, :generic, %{secret_key: "test_secret"}) - {"authorization", "Signature " <> signature} = List.keyfind(signed_config.headers, "authorization", 0) - - assert Signing.verify(config, signature, :hmac_sha256, %{secret_key: "test_secret"}) - end - - test "rejects invalid signature" do - config = %Config{ - method: :get, - url: "http://example.com/test", - headers: [{"content-type", "application/json"}], - body: ~s({"data": "test"}) - } - - refute Signing.verify(config, "invalid_signature", :hmac_sha256, %{secret_key: "test_secret"}) - end - end -end diff --git a/test/hreq/streaming_test.exs b/test/hreq/streaming_test.exs deleted file mode 100644 index f51bdb4..0000000 --- a/test/hreq/streaming_test.exs +++ /dev/null @@ -1,84 +0,0 @@ -defmodule HReq.StreamingTest do - use ExUnit.Case - alias HReq.{Streaming, Config} - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass} - end - - describe "streaming requests" do - test "streams response to memory", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/stream", fn conn -> - conn = Plug.Conn.send_chunked(conn, 200) - Enum.reduce_while(["chunk1", "chunk2", "chunk3"], conn, fn chunk, conn -> - case Plug.Conn.chunk(conn, chunk) do - {:ok, conn} -> - Process.sleep(50) # Simulate delay between chunks - {:cont, conn} - {:error, :closed} -> - {:halt, conn} - end - end) - end) - - url = "http://localhost:#{bypass.port}/stream" - config = Config.new() - - {on_chunk, on_done, get_result} = Streaming.to_memory() - - assert {:ok, response} = Streaming.request(:get, url, config, on_chunk, on_done) - assert response.status == 200 - - Process.sleep(200) # Wait for all chunks to be processed - assert get_result.() == "chunk1chunk2chunk3" - end - - test "streams response to file", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/stream", fn conn -> - conn = Plug.Conn.send_chunked(conn, 200) - Enum.reduce_while(["chunk1", "chunk2", "chunk3"], conn, fn chunk, conn -> - case Plug.Conn.chunk(conn, chunk) do - {:ok, conn} -> - Process.sleep(50) # Simulate delay between chunks - {:cont, conn} - {:error, :closed} -> - {:halt, conn} - end - end) - end) - - url = "http://localhost:#{bypass.port}/stream" - config = Config.new() - - path = "test_stream.txt" - on_exit(fn -> File.rm(path) end) - - {on_chunk, on_done} = Streaming.to_file(path) - - assert {:ok, response} = Streaming.request(:get, url, config, on_chunk, on_done) - assert response.status == 200 - - Process.sleep(200) # Wait for all chunks to be processed - assert File.read!(path) == "chunk1chunk2chunk3" - end - - test "handles streaming errors", %{bypass: bypass} do - Bypass.expect_once(bypass, "GET", "/stream", fn conn -> - conn = Plug.Conn.send_chunked(conn, 200) - Plug.Conn.chunk(conn, "chunk1") - end) - - url = "http://localhost:#{bypass.port}/stream" - config = Config.new() - - on_chunk = fn _chunk -> - {:error, :test_error} - end - - assert {:error, error} = Streaming.request(:get, url, config, on_chunk) - assert error.reason == :streaming_error - assert error.details == :test_error - end - end -end diff --git a/test/hreq/telemetry_test.exs b/test/hreq/telemetry_test.exs deleted file mode 100644 index 7adac0c..0000000 --- a/test/hreq/telemetry_test.exs +++ /dev/null @@ -1,115 +0,0 @@ -defmodule HReq.TelemetryTest do - use ExUnit.Case - alias HReq.{Telemetry, Config, Response} - - defmodule TestHandler do - def handle_event([:hreq, :request, :start], _measurements, metadata, _config) do - send(self(), {:request_start, metadata}) - end - - def handle_event([:hreq, :request, :stop], measurements, metadata, _config) do - send(self(), {:request_stop, measurements, metadata}) - end - - def handle_event([:hreq, :request, :exception], _measurements, metadata, _config) do - send(self(), {:request_exception, metadata}) - end - - def handle_event([:hreq, :batch, event], measurements, metadata, _config) - when event in [:start, :stop, :exception] do - send(self(), {:batch, event, measurements, metadata}) - end - end - - setup do - :ok = - :telemetry.attach_many( - "test-handler", - [ - [:hreq, :request, :start], - [:hreq, :request, :stop], - [:hreq, :request, :exception], - [:hreq, :batch, :start], - [:hreq, :batch, :stop], - [:hreq, :batch, :exception] - ], - &TestHandler.handle_event/4, - nil - ) - - :ok - end - - describe "request telemetry" do - test "emits events for successful requests" do - config = Config.new() - response = %Response{status: 200, body: "OK", headers: %{}} - - Telemetry.span(:get, "http://example.com", config, fn -> - {:ok, response} - end) - - assert_received {:request_start, metadata} - assert metadata.method == :get - assert metadata.url == "http://example.com" - - assert_received {:request_stop, measurements, metadata} - assert measurements.duration >= 0 - assert metadata.status == 200 - assert metadata.success == true - end - - test "emits events for failed requests" do - config = Config.new() - error = %HReq.Error{reason: :network_error} - - Telemetry.span(:get, "http://example.com", config, fn -> - {:error, error} - end) - - assert_received {:request_start, _metadata} - assert_received {:request_stop, _measurements, metadata} - assert metadata.error == :network_error - assert metadata.success == false - end - end - - describe "batch telemetry" do - test "emits events for batch requests" do - config = %{strategy: :parallel, base_url: "http://example.com"} - - responses = [ - %Response{status: 200, body: "OK1"}, - %Response{status: 200, body: "OK2"} - ] - - Telemetry.span_batch([1, 2], config, fn -> - {:ok, responses} - end) - - assert_received {:batch, :start, _measurements, metadata} - assert metadata.request_count == 2 - assert metadata.strategy == :parallel - - assert_received {:batch, :stop, _measurements, metadata} - assert metadata.success_count == 2 - assert metadata.success == true - end - - test "emits events for failed batch requests" do - config = %{strategy: :parallel, base_url: "http://example.com"} - error = %HReq.Error{reason: :batch_error} - - Telemetry.span_batch([1, 2], config, fn -> - {:error, error} - end) - - assert_received {:batch, :start, _measurements, metadata} - assert metadata.request_count == 2 - - assert_received {:batch, :stop, _measurements, metadata} - assert metadata.success == false - assert metadata.error_count == 1 - end - end -end diff --git a/test/hreq/utils_test.exs b/test/hreq/utils_test.exs deleted file mode 100644 index b0136cd..0000000 --- a/test/hreq/utils_test.exs +++ /dev/null @@ -1,53 +0,0 @@ -defmodule HReq.UtilsTest do - use ExUnit.Case - alias HReq.Utils - - describe "URL utilities" do - test "extracts links from HTML" do - html = """ - <html> - <body> - <a href="https://example.com">Link 1</a> - <a href='/page'>Link 2</a> - <a href="https://example.com">Duplicate</a> - </body> - </html> - """ - - links = Utils.extract_links(html) - assert length(links) == 2 - assert "https://example.com" in links - assert "/page" in links - end - - test "joins URLs correctly" do - assert Utils.join_urls("https://example.com", "path") == "https://example.com/path" - assert Utils.join_urls("https://example.com/", "/path") == "https://example.com/path" - assert Utils.join_urls("https://example.com/", "path") == "https://example.com/path" - end - - test "encodes query parameters" do - params = %{ - "key" => "value", - "array" => ["1", "2"], - "special" => "a b" - } - - encoded = Utils.encode_query(params) - assert encoded =~ "key=value" - assert encoded =~ "special=a+b" - end - - test "parses URLs" do - url = "https://user:pass@example.com:8080/path?query=value#fragment" - parsed = Utils.parse_url(url) - - assert parsed.scheme == "https" - assert parsed.host == "example.com" - assert parsed.port == 8080 - assert parsed.path == "/path" - assert parsed.query == "query=value" - assert parsed.fragment == "fragment" - end - end -end diff --git a/test/hreq/validator_test.exs b/test/hreq/validator_test.exs deleted file mode 100644 index 4e747e3..0000000 --- a/test/hreq/validator_test.exs +++ /dev/null @@ -1,180 +0,0 @@ -defmodule HReq.ValidatorTest do - use ExUnit.Case - alias HReq.{Validator, Response} - - @user_schema %{ - "type" => "object", - "required" => ["id", "name"], - "properties" => %{ - "id" => %{"type" => "integer"}, - "name" => %{"type" => "string"}, - "email" => %{"type" => "string", "format" => "email"} - } - } - - describe "schema validation" do - test "validates valid JSON response" do - response = %Response{ - status: 200, - headers: %{"content-type" => "application/json"}, - body: ~s({"id": 1, "name": "John", "email": "john@example.com"}) - } - - assert {:ok, ^response} = - Validator.validate(response, %{ - schema: @user_schema, - status_code: 200, - content_type: "application/json" - }) - end - - test "detects schema violations" do - response = %Response{ - status: 200, - headers: %{"content-type" => "application/json"}, - body: ~s({"id": "invalid", "name": 123}) - } - - assert {:error, error} = Validator.validate(response, %{schema: @user_schema}) - assert error.reason == :schema_validation_failed - assert length(error.errors) > 0 - end - - test "handles invalid JSON" do - response = %Response{ - status: 200, - headers: %{"content-type" => "application/json"}, - body: "invalid json" - } - - assert {:error, error} = Validator.validate(response, %{schema: @user_schema}) - assert error.reason == :invalid_json - end - end - - describe "status code validation" do - test "validates single status code" do - response = %Response{status: 201} - - assert {:ok, _} = Validator.validate(response, %{status_code: 201}) - assert {:error, error} = Validator.validate(response, %{status_code: 200}) - assert error.reason == :invalid_status - end - - test "validates multiple status codes" do - response = %Response{status: 201} - - assert {:ok, _} = Validator.validate(response, %{status_code: [200, 201]}) - assert {:error, _} = Validator.validate(response, %{status_code: [200, 204]}) - end - end - - describe "content type validation" do - test "validates exact content type" do - response = %Response{ - headers: %{"content-type" => "application/json; charset=utf-8"} - } - - assert {:ok, _} = Validator.validate(response, %{content_type: "application/json"}) - assert {:error, _} = Validator.validate(response, %{content_type: "text/plain"}) - end - - test "validates wildcard content type" do - response = %Response{ - headers: %{"content-type" => "application/json"} - } - - assert {:ok, _} = Validator.validate(response, %{content_type: "application/*"}) - assert {:error, _} = Validator.validate(response, %{content_type: "text/*"}) - end - - test "validates multiple content types" do - response = %Response{ - headers: %{"content-type" => "application/json"} - } - - assert {:ok, _} = - Validator.validate(response, %{ - content_type: ["application/json", "text/plain"] - }) - end - end - - describe "header validation" do - test "validates required headers" do - response = %Response{ - headers: %{ - "content-type" => "application/json", - "x-request-id" => "123" - } - } - - assert {:ok, _} = - Validator.validate(response, %{ - required_headers: ["content-type", "x-request-id"] - }) - - assert {:error, error} = - Validator.validate(response, %{ - required_headers: ["content-type", "x-missing"] - }) - - assert error.reason == :missing_headers - end - end - - describe "schema compilation" do - test "compiles valid schema" do - assert {:ok, schema} = Validator.compile_schema(@user_schema) - assert {:ok, schema} = Validator.compile_schema(Jason.encode!(@user_schema)) - end - - test "handles invalid schema" do - assert {:error, error} = Validator.compile_schema(%{"type" => "invalid"}) - assert error.reason == :invalid_schema - end - end - - describe "validate/2" do - test "validates status codes" do - response = %Response{status: 201} - - assert :ok = Validator.validate(response, %{status: [200..299]}) - assert {:error, _} = Validator.validate(response, %{status: [300..399]}) - end - - test "validates headers" do - response = %Response{ - status: 200, - headers: [{"content-type", "application/json"}] - } - - assert :ok = - Validator.validate(response, %{ - headers: %{"content-type" => ~r/application\/json/} - }) - - assert {:error, _} = - Validator.validate(response, %{ - headers: %{"content-type" => "text/plain"} - }) - end - - test "validates body" do - response = %Response{ - status: 200, - body: ~s({"data": "test"}) - } - - assert :ok = - Validator.validate(response, %{ - body: fn body -> String.contains?(body, "test") end - }) - - assert {:error, _} = - Validator.validate(response, %{ - body: fn body -> String.contains?(body, "invalid") end - }) - end - end -end diff --git a/test/hreq/websocket_test.exs b/test/hreq/websocket_test.exs deleted file mode 100644 index a3a84a7..0000000 --- a/test/hreq/websocket_test.exs +++ /dev/null @@ -1,94 +0,0 @@ -defmodule HReq.WebSocketTest do - use ExUnit.Case - alias HReq.WebSocket - - defmodule TestHandler do - @behaviour HReq.WebSocket.Handler - - def start_link do - Agent.start_link(fn -> [] end) - end - - def get_messages(pid) do - Agent.get(pid, & &1) - end - - def handle_ws(message) do - Agent.update(Process.get(:test_handler), fn messages -> - [message | messages] - end) - end - end - - setup do - {:ok, pid} = TestHandler.start_link() - Process.put(:test_handler, pid) - bypass = Bypass.open() - {:ok, bypass: bypass, handler_pid: pid} - end - - describe "websocket connection" do - test "establishes connection and handles messages", %{bypass: bypass, handler_pid: pid} do - Bypass.expect(bypass, "GET", "/ws", fn conn -> - conn = Plug.Conn.upgrade_adapter(conn, :websocket) - {:ok, conn} = Plug.Conn.send_chunked(conn, 101) - - # Simulate WebSocket upgrade - Process.send_after(self(), :send_message, 100) - {:ok, conn} - end) - - url = "ws://localhost:#{bypass.port}/ws" - {:ok, socket} = WebSocket.connect(url, TestHandler) - - # Wait for connection and message - Process.sleep(200) - - messages = TestHandler.get_messages(pid) - assert {:connected} in messages - - # Test sending a message - WebSocket.send_frame(socket, {:text, "hello"}) - Process.sleep(100) - - # Clean up - WebSocket.close(socket) - Process.sleep(100) - - messages = TestHandler.get_messages(pid) - assert {:closed} in messages - end - - test "handles connection errors", %{bypass: bypass} do - Bypass.down(bypass) - - url = "ws://localhost:#{bypass.port}/ws" - assert {:error, error} = WebSocket.connect(url, TestHandler) - assert error.reason == :connection_error - end - - test "handles disconnection", %{bypass: bypass, handler_pid: pid} do - Bypass.expect(bypass, "GET", "/ws", fn conn -> - conn = Plug.Conn.upgrade_adapter(conn, :websocket) - {:ok, conn} = Plug.Conn.send_chunked(conn, 101) - - # Simulate connection close - Process.send_after(self(), :close_connection, 100) - {:ok, conn} - end) - - url = "ws://localhost:#{bypass.port}/ws" - {:ok, _socket} = WebSocket.connect(url, TestHandler) - - # Wait for disconnection - Process.sleep(200) - - messages = TestHandler.get_messages(pid) - - assert Enum.any?(messages, fn - {:disconnected, _reason} -> true - _ -> false - end) - end - end -end diff --git a/test/hreq_test.exs b/test/hreq_test.exs deleted file mode 100644 index 97da4ab..0000000 --- a/test/hreq_test.exs +++ /dev/null @@ -1,139 +0,0 @@ -defmodule HReqTest do - use ExUnit.Case - doctest HReq - - alias HReq.{Response, Error} - - setup do - bypass = Bypass.open() - {:ok, bypass: bypass, url: "http://localhost:#{bypass.port}"} - end - - describe "request/6" do - test "successful GET request", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/test", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"data": "test"})) - end) - - assert {:ok, %Response{status: 200, body: body}} = HReq.get("#{url}/test") - assert Jason.decode!(body) == %{"data" => "test"} - end - - test "handles network errors", %{url: url} do - assert {:error, %Error{reason: :network_error}} = HReq.get("#{url}/invalid") - end - - test "handles invalid URLs" do - assert {:error, %Error{reason: :invalid_url}} = HReq.get("invalid-url") - end - end - - describe "compression" do - test "handles gzipped responses", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/gzip", fn conn -> - body = :zlib.gzip(~s({"data": "compressed"})) - conn - |> Plug.Conn.put_resp_header("content-encoding", "gzip") - |> Plug.Conn.resp(200, body) - end) - - assert {:ok, %Response{status: 200, body: body}} = - HReq.get("#{url}/gzip", [], [], compression: true) - assert Jason.decode!(body) == %{"data" => "compressed"} - end - end - - describe "caching" do - test "caches successful responses", %{bypass: bypass, url: url} do - Bypass.expect(bypass, "GET", "/cache", fn conn -> - Plug.Conn.resp(conn, 200, ~s({"data": "cached"})) - end) - - cache_opts = %{backend: :memory, ttl: 60} - - assert {:ok, response1} = HReq.get("#{url}/cache", [], [], cache: cache_opts) - assert {:ok, response2} = HReq.get("#{url}/cache", [], [], cache: cache_opts) - assert response1 == response2 - end - end - - describe "retry" do - test "retries failed requests", %{bypass: bypass, url: url} do - attempts = 0 - - Bypass.expect(bypass, "GET", "/retry", fn conn -> - attempts = attempts + 1 - if attempts < 3 do - Plug.Conn.resp(conn, 503, "") - else - Plug.Conn.resp(conn, 200, ~s({"data": "success"})) - end - end) - - retry_opts = %{max_attempts: 3, delay: 100} - assert {:ok, %Response{status: 200}} = - HReq.get("#{url}/retry", [], [], retry: retry_opts) - end - end - - describe "validation" do - test "validates response status", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/validate", fn conn -> - Plug.Conn.resp(conn, 201, ~s({"data": "valid"})) - end) - - validator_opts = %{status: [200..299]} - assert {:ok, _} = HReq.get("#{url}/validate", [], [], validator: validator_opts) - end - - test "validates response headers", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/headers", fn conn -> - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, ~s({"data": "valid"})) - end) - - validator_opts = %{ - headers: %{ - "content-type" => ~r/application\/json/ - } - } - assert {:ok, _} = HReq.get("#{url}/headers", [], [], validator: validator_opts) - end - end - - describe "oauth2" do - test "adds authorization header", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/oauth", fn conn -> - assert {"authorization", "Bearer test_token"} = - List.keyfind(conn.req_headers, "authorization", 0) - Plug.Conn.resp(conn, 200, "") - end) - - oauth2_opts = %HReq.OAuth2{ - token: "test_token", - expires_at: System.system_time(:second) + 3600 - } - - assert {:ok, _} = HReq.get("#{url}/oauth", [], [], oauth2: oauth2_opts) - end - end - - describe "signing" do - test "signs requests", %{bypass: bypass, url: url} do - Bypass.expect_once(bypass, "GET", "/signed", fn conn -> - assert {"authorization", "Signature " <> _} = - List.keyfind(conn.req_headers, "authorization", 0) - Plug.Conn.resp(conn, 200, "") - end) - - signing_opts = %{ - algorithm: :hmac_sha256, - provider: :generic, - credentials: %{secret_key: "test_key"} - } - - assert {:ok, _} = HReq.get("#{url}/signed", [], [], signing: signing_opts) - end - end -end